Skip to content

Commit 95be5e9

Browse files
OgeonX-AiAitomatesclaude
authored
feat: Phase 1+2 Background Autonomy — FS Watcher + Continuous Learning Pipeline (#2)
* feat(phase-1): real-time file system watcher (AUTO-01, AUTO-02) - FileWatcher class: chokidar v5 wrapper, two-layer noise filter - Ignores: node_modules, dist, .git, logs, tmp, coverage - Watches: ts, js, md, txt, prompt files - 5 new Vitest tests; 48 total passing - Wired into server init via RuntimeLogger + CommandCenterDashboard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: Phase 1 FS Watcher planning artifacts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: apply audit code-review fixes — semantic provider, structured responses, core refinements Prior audit session (F-01 through F-08) left code fixes unstaged. Includes semantic-provider.ts, structured-response.ts, and updates to config, dashboard, server, history, lesson extraction, and refiners. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: complete cross-cli prompt improvement platform * fix: synchronize npm 10 lockfile for CI * ci: adopt node 24 action runtimes * feat(autonomy): Phase 2 Continuous Learning Pipeline — GitPoller + AUTO-03/04 - GitPoller (src/history/git-poller.ts): polls CommitIngester.ingestLatest every 30s in production; emits commits event when new commits detected (AUTO-03) - BackgroundAutonomyService: wires GitPoller via optional gitPollIntervalMs param (null = disabled, safe for fake-timer tests); server.ts passes 30_000 (AUTO-04) - 8 new tests in git-poller.test.ts: poll/event/error/start/stop/idempotent - 122/122 tests green Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: add enterprise release verification gates * test: enforce complete deterministic coverage * test: isolate logger home fallback across platforms --------- Co-authored-by: Kim Harjamäki <kim.harjamaki@prosimo.fi> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent d732797 commit 95be5e9

82 files changed

Lines changed: 7039 additions & 346 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 123 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,149 @@
11
name: CI
22

33
on:
4+
workflow_dispatch:
45
push:
5-
branches: [ master ]
6+
branches: [ master, main ]
67
pull_request:
78

9+
permissions:
10+
contents: read
11+
812
jobs:
913
build-and-test:
1014
runs-on: ubuntu-latest
15+
timeout-minutes: 15
1116
defaults:
1217
run:
1318
working-directory: universal-refiner
1419
steps:
15-
- uses: actions/checkout@v4
20+
- uses: actions/checkout@v5
1621

1722
- name: Setup Node 22
18-
uses: actions/setup-node@v4
23+
uses: actions/setup-node@v5
1924
with:
2025
node-version: '22'
2126

2227
- name: Install dependencies
23-
run: npm install --no-fund
28+
run: npm ci --no-fund
2429

2530
- name: Rebuild native modules
2631
run: npm rebuild better-sqlite3
2732

28-
- name: Generate version file
29-
run: node scripts/sync-version.mjs
30-
31-
- name: Type check
32-
run: npx tsc --noEmit
33+
- name: Build
34+
run: npm run build
3335

3436
- name: Run tests
37+
run: npm run test:coverage
38+
39+
acceptance:
40+
runs-on: ubuntu-latest
41+
timeout-minutes: 15
42+
defaults:
43+
run:
44+
working-directory: universal-refiner
45+
strategy:
46+
fail-fast: false
47+
matrix:
48+
model-order:
49+
- primary
50+
- reversed
51+
steps:
52+
- uses: actions/checkout@v5
53+
- uses: actions/setup-node@v5
54+
with:
55+
node-version: '22'
56+
cache: npm
57+
cache-dependency-path: universal-refiner/package-lock.json
58+
- run: npm ci --no-fund
59+
- run: npm rebuild better-sqlite3
60+
- run: npm run build
61+
- name: Run all-tool and provider acceptance
62+
run: npm run test:acceptance
63+
- name: Run fake-model semantic acceptance
64+
env:
65+
PROMPT_REFINER_PRIMARY_MODEL: ${{ matrix.model-order == 'primary' && 'gemma3:12b' || 'gemma3:1b' }}
66+
PROMPT_REFINER_FALLBACK_MODEL: ${{ matrix.model-order == 'primary' && 'gemma3:1b' || 'gemma3:12b' }}
67+
run: npm run acceptance:semantic
68+
69+
stress:
70+
runs-on: ubuntu-latest
71+
timeout-minutes: 15
72+
defaults:
73+
run:
74+
working-directory: universal-refiner
75+
steps:
76+
- uses: actions/checkout@v5
77+
- uses: actions/setup-node@v5
78+
with:
79+
node-version: '22'
80+
cache: npm
81+
cache-dependency-path: universal-refiner/package-lock.json
82+
- run: npm ci --no-fund
83+
- run: npm rebuild better-sqlite3
84+
- run: npm run build
85+
- name: Run restart and in-process concurrency tests
86+
run: npm run test:stress
87+
- name: Run multi-process EventStore stress
88+
env:
89+
PROMPT_REFINER_STRESS_WORKERS: '4'
90+
PROMPT_REFINER_STRESS_WRITES: '100'
91+
run: npm run stress:event-store
92+
93+
windows:
94+
runs-on: windows-latest
95+
timeout-minutes: 20
96+
defaults:
97+
run:
98+
working-directory: universal-refiner
99+
steps:
100+
- uses: actions/checkout@v5
101+
- uses: actions/setup-node@v5
102+
with:
103+
node-version: '22'
104+
cache: npm
105+
cache-dependency-path: universal-refiner/package-lock.json
106+
- run: npm ci --no-fund
107+
- run: npm rebuild better-sqlite3
108+
- run: npm run build
109+
- run: npm run test:coverage
110+
- run: npm run test:acceptance
111+
- run: npm run test:stress
112+
113+
supply-chain:
114+
runs-on: ubuntu-latest
115+
timeout-minutes: 15
116+
defaults:
117+
run:
118+
working-directory: universal-refiner
119+
steps:
120+
- uses: actions/checkout@v5
121+
- uses: actions/setup-node@v5
122+
with:
123+
node-version: '22'
124+
cache: npm
125+
cache-dependency-path: universal-refiner/package-lock.json
126+
- run: npm ci --no-fund
127+
- run: npm run security:audit
128+
- run: npm run security:secrets
129+
- run: npm run build
130+
- run: npm run package:check
131+
132+
release-gate:
133+
if: always()
134+
needs: [build-and-test, acceptance, stress, windows, supply-chain]
135+
runs-on: ubuntu-latest
136+
steps:
137+
- name: Require every enterprise gate
138+
env:
139+
BUILD: ${{ needs['build-and-test'].result }}
140+
ACCEPTANCE: ${{ needs.acceptance.result }}
141+
STRESS: ${{ needs.stress.result }}
142+
WINDOWS: ${{ needs.windows.result }}
143+
SUPPLY_CHAIN: ${{ needs['supply-chain'].result }}
35144
run: |
36-
chmod +x node_modules/.bin/vitest 2>/dev/null || true
37-
node_modules/.bin/vitest run --exclude '**/correlation.test.ts'
145+
test "$BUILD" = "success"
146+
test "$ACCEPTANCE" = "success"
147+
test "$STRESS" = "success"
148+
test "$WINDOWS" = "success"
149+
test "$SUPPLY_CHAIN" = "success"

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Dependencies and generated outputs
22
**/node_modules/
33
**/dist/
4+
**/coverage/
45
*.tgz
56

67
# Runtime state and local databases
@@ -23,4 +24,4 @@
2324
.vscode/
2425
.idea/
2526
.DS_Store
26-
Thumbs.db
27+
Thumbs.db
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
---
2+
phase: 01-fs-watcher
3+
plan: 01
4+
type: execute
5+
wave: 1
6+
depends_on: []
7+
files_modified:
8+
- universal-refiner/src/watcher/file-watcher.ts
9+
- universal-refiner/src/watcher/index.ts
10+
- universal-refiner/tests/file-watcher.test.ts
11+
- universal-refiner/src/index.ts
12+
autonomous: true
13+
requirements:
14+
- AUTO-01
15+
- AUTO-02
16+
must_haves:
17+
truths:
18+
- "FileWatcher emits a 'change' event when a watched .ts file is written"
19+
- "FileWatcher emits an 'add' event when a new .ts file appears"
20+
- "FileWatcher does not emit for files inside node_modules"
21+
- "FileWatcher does not emit for *.log or *.tmp files"
22+
- "FileWatcher.stop() prevents any further events"
23+
- "Detected changes are logged via RuntimeLogger on server startup"
24+
artifacts:
25+
- path: "universal-refiner/src/watcher/file-watcher.ts"
26+
provides: "FileWatcher class with start/stop/on('change') interface"
27+
exports: ["FileWatcher", "FileChangeEvent", "FileEventKind"]
28+
- path: "universal-refiner/src/watcher/index.ts"
29+
provides: "Re-exports for the watcher module"
30+
- path: "universal-refiner/tests/file-watcher.test.ts"
31+
provides: "5 Vitest tests covering AUTO-01 and AUTO-02"
32+
key_links:
33+
- from: "universal-refiner/src/index.ts"
34+
to: "universal-refiner/src/watcher/index.ts"
35+
via: "import FileWatcher, call start() at server init"
36+
---
37+
38+
<objective>
39+
Implement a real-time file system watcher (Phase 1) for the universal-refiner MCP server.
40+
41+
Purpose: Satisfy AUTO-01 (detect meaningful file save events) and AUTO-02 (filter noise paths) as the foundation for the Background Autonomy milestone.
42+
43+
Output:
44+
- src/watcher/file-watcher.ts — FileWatcher class wrapping chokidar v5
45+
- src/watcher/index.ts — re-exports
46+
- tests/file-watcher.test.ts — 5 passing Vitest tests
47+
- src/index.ts updated to start watcher on server init
48+
</objective>
49+
50+
<execution_context>
51+
chokidar v5.0.0 is already in dependencies. No new packages needed.
52+
Uses RuntimeLogger (stderr, JSON-RPC safe) for all output.
53+
</execution_context>
54+
55+
<context>
56+
@.planning/ROADMAP.md
57+
@.planning/REQUIREMENTS.md
58+
@universal-refiner/src/core/logger.ts
59+
@universal-refiner/src/index.ts
60+
</context>
61+
62+
<tasks>
63+
64+
<task type="auto">
65+
<name>Task 1: Create FileWatcher module (AUTO-01, AUTO-02)</name>
66+
<files>
67+
universal-refiner/src/watcher/file-watcher.ts
68+
universal-refiner/src/watcher/index.ts
69+
</files>
70+
<action>
71+
FileWatcher extends EventEmitter. Constructor takes rootPath: string.
72+
start(): watches rootPath via chokidar.watch() with ignored: CHOKIDAR_IGNORE patterns.
73+
stop(): closes the chokidar watcher, nulls inner reference.
74+
emitChange() applies two-layer filter before emitting:
75+
1. Path segment check: reject paths containing /node_modules/, /dist/, /.git/, /coverage/
76+
2. Extension check: only emit for .ts, .js, .md, .txt, .prompt
77+
3. Suffix noise check: reject .log, .tmp
78+
Emits: { path: string, event: 'add'|'change'|'unlink', timestamp: Date }
79+
Logs start/stop and per-event debug via RuntimeLogger.
80+
index.ts re-exports FileWatcher, FileChangeEvent, FileEventKind.
81+
</action>
82+
<verify>npm run build -- succeeds with zero type errors</verify>
83+
<done>Both files exist, TypeScript compiles clean, exports are correct.</done>
84+
</task>
85+
86+
<task type="auto" tdd="true">
87+
<name>Task 2: Write Vitest tests (AUTO-01, AUTO-02)</name>
88+
<files>universal-refiner/tests/file-watcher.test.ts</files>
89+
<behavior>
90+
- Test: write to existing .ts file in tmp dir -> 'change' event emitted with correct path and Date timestamp
91+
- Test: write new .ts file -> 'add' event emitted
92+
- Test: write .ts file inside node_modules subdirectory -> no event emitted (AUTO-02)
93+
- Test: write .log file -> no event emitted (AUTO-02)
94+
- Test: stop() called -> subsequent file write produces no events
95+
</behavior>
96+
<action>
97+
Use vitest describe/it/expect. beforeEach creates a unique tmp dir via fs.mkdtempSync.
98+
afterEach calls watcher.stop() and fs.rmSync.
99+
Use a polling waitFor() helper with 6000ms timeout for positive assertions.
100+
Allow 1500ms settle time after watcher.start() before writing files (Windows FS listener warm-up).
101+
Set per-test timeout to 15_000.
102+
</action>
103+
<verify>npm test -- shows 5/5 file-watcher tests passing</verify>
104+
<done>All 48 total tests pass including the 5 new watcher tests.</done>
105+
</task>
106+
107+
<task type="auto">
108+
<name>Task 3: Wire FileWatcher into server entry point</name>
109+
<files>universal-refiner/src/index.ts</files>
110+
<action>
111+
Import FileWatcher from "./watcher/index.js".
112+
After CommandCenterDashboard.start() and before runBackgroundTasks():
113+
const fileWatcher = new FileWatcher(rootPath);
114+
fileWatcher.on('change', (evt) => {
115+
RuntimeLogger.info(`[FS] ${evt.event}: ${evt.path}`);
116+
CommandCenterDashboard.log(`[FS] ${evt.event}: ${path.relative(rootPath, evt.path)}`);
117+
});
118+
fileWatcher.start();
119+
BackgroundAutonomyService is left intact — FileWatcher is additive.
120+
</action>
121+
<verify>npm run build succeeds; server starts without error</verify>
122+
<done>File watcher starts automatically when the MCP server initialises.</done>
123+
</task>
124+
125+
</tasks>
126+
127+
<threat_model>
128+
## Trust Boundaries
129+
130+
| Boundary | Description |
131+
|----------|-------------|
132+
| FS path → emitChange | File paths from chokidar are OS-provided and not sanitised before logging |
133+
134+
## STRIDE Threat Register
135+
136+
| Threat ID | Category | Component | Disposition | Mitigation Plan |
137+
|-----------|----------|-----------|-------------|-----------------|
138+
| T-01-01 | Information Disclosure | RuntimeLogger path output | accept | Logs go to stderr/runtime.log, not stdout (JSON-RPC channel). No PII in paths. |
139+
| T-01-02 | Denial of Service | Rapid file changes flood emitChange | mitigate | awaitWriteFinish debounce (100ms stability) prevents event storms. |
140+
| T-01-03 | Tampering | Malicious path with path-traversal characters | accept | FileWatcher is read-only (no FS writes). Path is logged, not executed. |
141+
</threat_model>
142+
143+
<verification>
144+
- npm run build -- zero TypeScript errors
145+
- npm test -- 48/48 tests pass (5 new FileWatcher tests)
146+
- Server starts and logs "[FileWatcher] Starting file system watcher" on init
147+
</verification>
148+
149+
<success_criteria>
150+
1. Writing a .ts file in the watched directory emits a change event within 3 seconds.
151+
2. Files under node_modules, dist, .git, coverage are never emitted.
152+
3. .log and .tmp files are never emitted.
153+
4. stop() terminates all event delivery immediately.
154+
5. Build and full test suite remain green.
155+
</success_criteria>
156+
157+
<output>
158+
Create .planning/phases/01-fs-watcher/01-01-SUMMARY.md after execution.
159+
</output>

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,30 @@ cd Promptimprover
7979

8080
Both installers perform a deterministic dependency install, run the full test suite, build the package, install it globally, and verify the `gemini-prompt-refiner` command. Add that command to your MCP client configuration. See the [Setup Guide](https://github.com/Coding-Autopilot-System/Promptimprover/wiki/Setup-Guide) for full configuration instructions.
8181

82+
For optional automatic pre-prompt linting and post-execution recording, see the [cross-CLI automation guide](./docs/cross-cli-automation.md). Claude Code and Gemini CLI expose the required lifecycle hooks. Codex currently requires MCP-first instructions or explicit helper invocation because its hook lifecycle does not transparently intercept each prompt.
83+
84+
## Local Semantic Model
85+
86+
PromptImprover uses a local OpenAI-compatible endpoint before optional MCP sampling. The safe defaults target `http://localhost:9000/v1`, use `gemma3:12b` first, and fall back to `gemma3:1b`. If neither local model nor MCP sampling is available, rule-based refinement continues without semantic output.
87+
88+
Override the defaults per repository with `.gemini-refiner.json`:
89+
90+
```json
91+
{
92+
"semantic": {
93+
"localEnabled": true,
94+
"mcpSamplingEnabled": true,
95+
"baseUrl": "http://localhost:9000/v1",
96+
"models": ["gemma3:12b", "gemma3:1b"],
97+
"timeoutMs": 120000,
98+
"temperature": 0.2,
99+
"allowNonLoopback": false
100+
}
101+
}
102+
```
103+
104+
Non-loopback model endpoints are rejected unless `allowNonLoopback` is explicitly enabled. Generated lessons and templates remain pending until reviewed through the MCP learning-review tools.
105+
82106
## License
83107

84108
MIT - see [LICENSE](LICENSE)

docs/cross-cli-automation.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Cross-CLI Automation
2+
3+
PromptImprover ships fail-open pre-prompt and post-execution helpers:
4+
5+
- `promptimprover-hook-pre` makes one latency-safe rule-based `lint_prompt` call, creates a trackable prompt ID, and injects advisory context. Interactive MCP linting continues to use semantic providers by default.
6+
- `promptimprover-hook-post` records privacy-safe completion metadata with `record_agent_output`.
7+
8+
Both commands read hook JSON from stdin, write JSON only to stdout, report failures to stderr, and always allow the client to continue. They start the same built MCP server used by `gemini-prompt-refiner`. Set `PROMPTIMPROVER_SERVER_PATH` only when testing a nonstandard build.
9+
10+
The helpers store only prompt ID, client name, and creation time in the OS temporary directory. They do not persist prompt or response bodies. Completion records contain output length rather than response text.
11+
12+
## Claude Code
13+
14+
Claude Code supports `UserPromptSubmit` and `Stop`, so both phases can run transparently. Merge [`claude.settings.fragment.json`](../universal-refiner/hooks/config/claude.settings.fragment.json) into the desired user or project settings file after installing the package globally.
15+
16+
## Gemini CLI
17+
18+
Gemini CLI supports `BeforeAgent` and `AfterAgent`, so both phases can run transparently. Merge [`gemini.settings.fragment.json`](../universal-refiner/hooks/config/gemini.settings.fragment.json) into the desired user or project settings file after installing the package globally.
19+
20+
## Codex CLI
21+
22+
Codex CLI `0.138.0` has a stable hook system, but its exposed lifecycle currently does not provide transparent per-prompt pre/post hooks. Do not claim that `SessionStart` performs prompt interception.
23+
24+
Keep PromptImprover registered as an MCP server and use repo instructions that require `lint_prompt` and `record_agent_output`. External automation can pipe normalized JSON into the same helpers; see [`codex.config.fragment.toml`](../universal-refiner/hooks/config/codex.config.fragment.toml).
25+
26+
## Failure And Privacy Behavior
27+
28+
- MCP startup, timeout, parsing, and tool errors fail open.
29+
- Default timeout is 15 seconds; set `PROMPTIMPROVER_HOOK_TIMEOUT_MS` to a positive millisecond value to change it.
30+
- Hook stdout remains strict JSON.
31+
- No credentials or environment values are read or logged.
32+
- Prompt and response text are not written to hook state or completion summaries.

0 commit comments

Comments
 (0)