@ai-content-space/loopx 0.2.4 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +106 -10
  2. package/README.zh-CN.md +106 -10
  3. package/docs/loopx/design/finish/345/255/246/344/271/240/345/256/241/350/256/241/351/234/200/346/261/202/350/256/276/350/256/241/346/226/207/346/241/243.md +707 -0
  4. package/docs/loopx/memory/2026-06-09-stale-archive-hook-guidance.md +15 -0
  5. package/docs/loopx/memory/README.md +25 -0
  6. package/docs/loopx/plans/2026-06-08-finish-audit-change-window.md +933 -0
  7. package/docs/loopx/plans/2026-06-08-finish-learning-audit.md +410 -0
  8. package/docs/loopx/plans/2026-06-09-cli-onboarding-install-surface.md +1277 -0
  9. package/docs/loopx/specs/installation.md +33 -0
  10. package/package.json +18 -2
  11. package/plugins/loopx/.codex-plugin/plugin.json +1 -1
  12. package/plugins/loopx/skills/clarify/SKILL.md +1 -1
  13. package/plugins/loopx/skills/debug/SKILL.md +1 -1
  14. package/plugins/loopx/skills/doc-readability/SKILL.md +1 -1
  15. package/plugins/loopx/skills/exec/SKILL.md +11 -1
  16. package/plugins/loopx/skills/final-review/SKILL.md +1 -1
  17. package/plugins/loopx/skills/finish/SKILL.md +39 -7
  18. package/plugins/loopx/skills/fix-review/SKILL.md +1 -1
  19. package/plugins/loopx/skills/go-style/SKILL.md +1 -1
  20. package/plugins/loopx/skills/kratos/SKILL.md +1 -1
  21. package/plugins/loopx/skills/plan/SKILL.md +1 -1
  22. package/plugins/loopx/skills/refactor-plan/SKILL.md +1 -1
  23. package/plugins/loopx/skills/review/SKILL.md +1 -1
  24. package/plugins/loopx/skills/spec/SKILL.md +1 -1
  25. package/plugins/loopx/skills/subagent-exec/SKILL.md +13 -1
  26. package/plugins/loopx/skills/tdd/SKILL.md +1 -1
  27. package/plugins/loopx/skills/verify/SKILL.md +1 -1
  28. package/scripts/claude-workflow-hook.mjs +50 -1
  29. package/scripts/codex-workflow-hook.mjs +33 -12
  30. package/scripts/install-skills.mjs +58 -3
  31. package/scripts/verify-skills.mjs +83 -7
  32. package/skills/clarify/SKILL.md +1 -1
  33. package/skills/debug/SKILL.md +1 -1
  34. package/skills/doc-readability/SKILL.md +1 -1
  35. package/skills/exec/SKILL.md +11 -1
  36. package/skills/final-review/SKILL.md +1 -1
  37. package/skills/finish/SKILL.md +39 -7
  38. package/skills/fix-review/SKILL.md +1 -1
  39. package/skills/go-style/SKILL.md +1 -1
  40. package/skills/kratos/SKILL.md +1 -1
  41. package/skills/plan/SKILL.md +1 -1
  42. package/skills/refactor-plan/SKILL.md +1 -1
  43. package/skills/review/SKILL.md +1 -1
  44. package/skills/spec/SKILL.md +1 -1
  45. package/skills/subagent-exec/SKILL.md +13 -1
  46. package/skills/tdd/SKILL.md +1 -1
  47. package/skills/verify/SKILL.md +1 -1
  48. package/src/cli.mjs +473 -86
  49. package/src/finish-runtime.mjs +1184 -0
  50. package/src/install-discovery.mjs +37 -0
  51. package/src/next-skill.mjs +8 -10
  52. package/src/workflow.mjs +19 -26
  53. package/skills/deepsearch/SKILL.md +0 -38
@@ -0,0 +1,410 @@
1
+ # finish 学习审计 Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use loopx:subagent-exec (recommended) or loopx:exec to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Source:** [docs/loopx/design/finish学习审计需求设计文档.md](../design/finish学习审计需求设计文档.md)
6
+
7
+ **Goal:** Add a local finish audit ledger and choice recorder so finish always leaves explainable learning evidence, rejected-candidate reasons, and a persisted user completion choice.
8
+
9
+ **Architecture:** Keep dangerous completion actions in the skill flow. Add a small runtime module that creates `.loopx/finish/<audit-id>/finish-state.json` and `finish-report.md`, plus a record command that updates the same audit directory with the final action/status. The finish skill and its plugin mirror become stricter about running audit first, surfacing candidates and reasons, and recording the final choice. Repo-tracked spec candidates remain `docs/loopx/specs/<domain>.md`.
10
+
11
+ **Tech Stack:** Node.js ESM, `node:test`, `node:fs/promises`, existing loopx workflow and skill governance tests.
12
+
13
+ ---
14
+
15
+ ### Task 1: Add finish audit runtime primitives
16
+
17
+ **Files:**
18
+ - Create: `src/finish-runtime.mjs`
19
+ - Test: `test/trellis-hardening.test.mjs`
20
+
21
+ - [ ] **Step 1: Write the failing test**
22
+
23
+ ```js
24
+ import { mkdtemp, readFile } from 'node:fs/promises';
25
+ import { tmpdir } from 'node:os';
26
+ import { join } from 'node:path';
27
+ import { strict as assert } from 'node:assert';
28
+ import { execFile } from 'node:child_process';
29
+ import { describe, it } from 'node:test';
30
+ import { promisify } from 'node:util';
31
+
32
+ import { finishAuditStage } from '../src/finish-runtime.mjs';
33
+
34
+ const execFileAsync = promisify(execFile);
35
+
36
+ it('creates a finish audit directory with state and report files', async () => {
37
+ const wd = await mkdtemp(join(tmpdir(), 'loopx-finish-audit-'));
38
+ await execFileAsync('git', ['init'], { cwd: wd });
39
+ const result = await finishAuditStage(wd, 'finish-audit-flow');
40
+
41
+ assert.match(result.auditId, /^\d{8}T\d{6}Z-finish-audit-flow$/);
42
+ assert.equal(result.state.status, 'needs-agent-audit');
43
+ assert.equal(await readFile(result.statePath, 'utf8').then((text) => text.includes('"audit_id"')), true);
44
+ assert.equal(await readFile(result.reportPath, 'utf8').then((text) => text.includes('Finish Audit')), true);
45
+ });
46
+ ```
47
+
48
+ - [ ] **Step 2: Run test to verify it fails**
49
+
50
+ Run: `node --test test/trellis-hardening.test.mjs -t "creates a finish audit directory with state and report files"`
51
+ Expected: FAIL because `src/finish-runtime.mjs` does not exist yet.
52
+
53
+ - [ ] **Step 3: Write minimal implementation**
54
+
55
+ Implement `src/finish-runtime.mjs` with:
56
+
57
+ ```js
58
+ export function resolveFinishAuditRoot(cwd) {}
59
+ export function resolveFinishAuditPath(cwd, auditId) {}
60
+ export async function finishAuditStage(cwd, slug, { env = process.env } = {}) {}
61
+ ```
62
+
63
+ Required behavior:
64
+ - create `.loopx/finish/<audit-id>/`
65
+ - write `finish-state.json` with `schema_version`, `audit_id`, `slug`, `status`, `inputs.scanned`, `audit`, `choice`
66
+ - write `finish-report.md` with human-readable sections for summary, scanned inputs, accepted/rejected candidates, and next steps
67
+ - collect git branch/base-branch/worktree evidence defensively, but allow `unknown`/`unavailable` fields when the data cannot be read
68
+ - do not write `.loopx/memory` or `docs/loopx/specs` yet
69
+
70
+ - [ ] **Step 4: Run test to verify it passes**
71
+
72
+ Run: `node --test test/trellis-hardening.test.mjs -t "creates a finish audit directory with state and report files"`
73
+ Expected: PASS
74
+
75
+ - [ ] **Step 5: Commit**
76
+
77
+ ```bash
78
+ git add src/finish-runtime.mjs test/trellis-hardening.test.mjs
79
+ git commit -m "feat: add finish audit runtime primitives"
80
+ ```
81
+
82
+ ### Task 2: Add finish choice recording
83
+
84
+ **Files:**
85
+ - Modify: `src/finish-runtime.mjs`
86
+ - Test: `test/trellis-hardening.test.mjs`
87
+
88
+ - [ ] **Step 1: Write the failing test**
89
+
90
+ ```js
91
+ import { mkdtemp, readFile, writeFile } from 'node:fs/promises';
92
+ import { tmpdir } from 'node:os';
93
+ import { join } from 'node:path';
94
+ import { strict as assert } from 'node:assert';
95
+ import { execFile } from 'node:child_process';
96
+ import { promisify } from 'node:util';
97
+
98
+ import { finishAuditStage, finishRecordStage } from '../src/finish-runtime.mjs';
99
+
100
+ const execFileAsync = promisify(execFile);
101
+
102
+ it('records a finish choice and refuses done before audit is complete', async () => {
103
+ const wd = await mkdtemp(join(tmpdir(), 'loopx-finish-record-'));
104
+ await execFileAsync('git', ['init'], { cwd: wd });
105
+ const audit = await finishAuditStage(wd, 'finish-record-flow');
106
+
107
+ await assert.rejects(
108
+ () => finishRecordStage(wd, audit.auditId, { action: 'keep', status: 'done', summary: 'Kept as-is.' }),
109
+ /finish_record_audit_incomplete/,
110
+ );
111
+
112
+ const state = JSON.parse(await readFile(audit.statePath, 'utf8'));
113
+ state.status = 'audited';
114
+ state.audit.accepted_candidates = [{
115
+ kind: 'memory',
116
+ type: 'decision',
117
+ domain: 'workflow',
118
+ summary: 'finish records durable learning evidence',
119
+ evidence: ['skills/finish/SKILL.md'],
120
+ future_usefulness: 'Future agents can confirm finish audits were actually recorded.',
121
+ target: '.loopx/memory/entries/finish-record.md',
122
+ confidence: 'high',
123
+ status: 'accepted',
124
+ }];
125
+ await writeFile(audit.statePath, `${JSON.stringify(state, null, 2)}\n`);
126
+
127
+ const completed = await finishRecordStage(wd, audit.auditId, {
128
+ action: 'keep',
129
+ status: 'done',
130
+ summary: 'Kept branch as-is.',
131
+ });
132
+
133
+ assert.equal(completed.state.choice.action, 'keep');
134
+ assert.equal(completed.state.choice.status, 'done');
135
+ });
136
+ ```
137
+
138
+ - [ ] **Step 2: Run test to verify it fails**
139
+
140
+ Run: `node --test test/trellis-hardening.test.mjs -t "records a finish choice and refuses done before audit is complete"`
141
+ Expected: FAIL because `finishRecordStage` is not implemented yet.
142
+
143
+ - [ ] **Step 3: Write minimal implementation**
144
+
145
+ Extend `src/finish-runtime.mjs` with:
146
+
147
+ ```js
148
+ export async function finishRecordStage(cwd, auditIdOrPath, { action, status, summary = null, url = null, env = process.env } = {}) {}
149
+ ```
150
+
151
+ Required behavior:
152
+ - resolve audit dir from ID or direct path
153
+ - validate `action` in `merge|pr|keep|discard`
154
+ - validate `status` in `pending|done|failed|aborted`
155
+ - refuse `status: done` unless audit state is complete enough to be considered audited
156
+ - update `choice`, `choice_history`, `updated_at`, and `finish-report.md`
157
+ - preserve previous choice entries when the action changes
158
+
159
+ - [ ] **Step 4: Run test to verify it passes**
160
+
161
+ Run: `node --test test/trellis-hardening.test.mjs -t "records a finish choice and refuses done before audit is complete"`
162
+ Expected: PASS
163
+
164
+ - [ ] **Step 5: Commit**
165
+
166
+ ```bash
167
+ git add src/finish-runtime.mjs test/trellis-hardening.test.mjs
168
+ git commit -m "feat: record finish completion choices"
169
+ ```
170
+
171
+ ### Task 3: Wire finish commands into the CLI
172
+
173
+ **Files:**
174
+ - Modify: `src/cli.mjs`
175
+ - Modify: `src/finish-runtime.mjs`
176
+ - Test: `test/trellis-hardening.test.mjs`
177
+
178
+ - [ ] **Step 1: Write the failing test**
179
+
180
+ ```js
181
+ import { mkdtemp } from 'node:fs/promises';
182
+ import { tmpdir } from 'node:os';
183
+ import { join } from 'node:path';
184
+ import { execFile } from 'node:child_process';
185
+ import { promisify } from 'node:util';
186
+
187
+ const execFileAsync = promisify(execFile);
188
+
189
+ it('exposes finish-audit and finish-record CLI commands', async () => {
190
+ const wd = await mkdtemp(join(tmpdir(), 'loopx-finish-cli-'));
191
+ await execFileAsync('git', ['init'], { cwd: wd });
192
+
193
+ const audit = await execFileAsync(process.execPath, [cliPath, 'finish-audit', 'finish-cli-flow'], { cwd: wd });
194
+ assert.match(audit.stdout, /audit_id/);
195
+ assert.match(audit.stdout, /finish-report\.md/);
196
+
197
+ const auditId = JSON.parse(audit.stdout).audit_id;
198
+ const record = await execFileAsync(process.execPath, [
199
+ cliPath,
200
+ 'finish-record',
201
+ auditId,
202
+ '--action',
203
+ 'keep',
204
+ '--status',
205
+ 'pending',
206
+ '--summary',
207
+ 'Kept branch as-is.',
208
+ ], { cwd: wd });
209
+ assert.match(record.stdout, /choice/);
210
+ });
211
+ ```
212
+
213
+ - [ ] **Step 2: Run test to verify it fails**
214
+
215
+ Run: `node --test test/trellis-hardening.test.mjs -t "exposes finish-audit and finish-record CLI commands"`
216
+ Expected: FAIL because `src/cli.mjs` does not route these commands yet.
217
+
218
+ - [ ] **Step 3: Write minimal implementation**
219
+
220
+ Update `src/cli.mjs`:
221
+ - import `finishAuditStage` and `finishRecordStage`
222
+ - add `loopx finish-audit [slug] [--json]`
223
+ - add `loopx finish-record <audit-id-or-path> --action ... --status ... [--summary ...] [--url ...]`
224
+ - keep JSON output consistent with other commands
225
+ - return stable error codes via `process.exitCode = 1` on validation failure
226
+ - keep `finish-record` focused on choice persistence; do not add a third finish command
227
+
228
+ - [ ] **Step 4: Run test to verify it passes**
229
+
230
+ Run: `node --test test/trellis-hardening.test.mjs -t "exposes finish-audit and finish-record CLI commands"`
231
+ Expected: PASS
232
+
233
+ - [ ] **Step 5: Commit**
234
+
235
+ ```bash
236
+ git add src/cli.mjs src/finish-runtime.mjs test/trellis-hardening.test.mjs
237
+ git commit -m "feat: add finish audit CLI commands"
238
+ ```
239
+
240
+ ### Task 4: Tighten finish skill docs and plugin mirror
241
+
242
+ **Files:**
243
+ - Modify: `skills/finish/SKILL.md`
244
+ - Modify: `plugins/loopx/skills/finish/SKILL.md`
245
+ - Test: `test/skill-governance.test.mjs`
246
+
247
+ - [ ] **Step 1: Write the failing test**
248
+
249
+ ```js
250
+ it('requires finish audit and choice recording in the finish skill docs', async () => {
251
+ const finish = await readFile(join(repoRoot, 'skills', 'finish', 'SKILL.md'), 'utf8');
252
+ assert.match(finish, /finish-audit/);
253
+ assert.match(finish, /finish-record/);
254
+ assert.match(finish, /no_candidates_reason/);
255
+ assert.match(finish, /rejected candidates/);
256
+ assert.match(finish, /choice recording/);
257
+ });
258
+ ```
259
+
260
+ - [ ] **Step 2: Run test to verify it fails**
261
+
262
+ Run: `node --test test/skill-governance.test.mjs -t "requires finish audit and choice recording in the finish skill docs"`
263
+ Expected: FAIL because the current skill text only describes generic learning extraction.
264
+
265
+ - [ ] **Step 3: Write minimal implementation**
266
+
267
+ Update both skill mirrors with:
268
+ - audit-first flow in Step 4
269
+ - explicit mention of `.loopx/finish/<audit-id>/finish-state.json`
270
+ - explicit mention that `none` must include scanned inputs and a reason
271
+ - explicit mention that accepted candidates require evidence and rejected candidates require reasons
272
+ - explicit mention that user completion choice must be persisted through `finish-record`
273
+ - keep wording bounded and operational, no new product scope
274
+
275
+ - [ ] **Step 4: Run test to verify it passes**
276
+
277
+ Run: `node --test test/skill-governance.test.mjs -t "requires finish audit and choice recording in the finish skill docs"`
278
+ Expected: PASS
279
+
280
+ - [ ] **Step 5: Commit**
281
+
282
+ ```bash
283
+ git add skills/finish/SKILL.md plugins/loopx/skills/finish/SKILL.md test/skill-governance.test.mjs
284
+ git commit -m "docs: tighten finish learning audit flow"
285
+ ```
286
+
287
+ ### Task 5: Add README and command-reference coverage
288
+
289
+ **Files:**
290
+ - Modify: `README.md`
291
+ - Modify: `README.zh-CN.md`
292
+ - Test: `scripts/verify-skills.mjs`
293
+
294
+ - [ ] **Step 1: Write the failing test**
295
+
296
+ ```js
297
+ it('documents finish audit and record commands in the public docs', async () => {
298
+ const readme = await readFile(join(repoRoot, 'README.md'), 'utf8');
299
+ assert.match(readme, /finish-audit/);
300
+ assert.match(readme, /finish-record/);
301
+ assert.match(readme, /\.loopx\/finish\//);
302
+ });
303
+ ```
304
+
305
+ - [ ] **Step 2: Run test to verify it fails**
306
+
307
+ Run: `node scripts/verify-skills.mjs`
308
+ Expected: FAIL until the README references are added.
309
+
310
+ - [ ] **Step 3: Write minimal implementation**
311
+
312
+ Update docs to explain:
313
+ - finish now writes a local audit ledger
314
+ - `none` means audited, but no durable learning candidate
315
+ - choice recording lives in the local finish audit directory
316
+ - repo-tracked spec candidates stay in `docs/loopx/specs/`
317
+ - public docs mention `loopx finish-audit` and `loopx finish-record`
318
+
319
+ - [ ] **Step 4: Run test to verify it passes**
320
+
321
+ Run: `node scripts/verify-skills.mjs`
322
+ Expected: PASS
323
+
324
+ - [ ] **Step 5: Commit**
325
+
326
+ ```bash
327
+ git add README.md README.zh-CN.md scripts/verify-skills.mjs
328
+ git commit -m "docs: document finish audit ledger"
329
+ ```
330
+
331
+ ### Task 6: Add focused regression coverage for none/rejected-candidate behavior
332
+
333
+ **Files:**
334
+ - Modify: `test/trellis-hardening.test.mjs`
335
+ - Modify: `test/skill-governance.test.mjs`
336
+
337
+ - [ ] **Step 1: Write the failing test**
338
+
339
+ ```js
340
+ it('keeps finish audit explainable when no memory or spec candidates are accepted', async () => {
341
+ const wd = await mkdtemp(join(tmpdir(), 'loopx-finish-none-'));
342
+ const audit = await finishAuditStage(wd, 'finish-none-flow');
343
+ const state = JSON.parse(await readFile(audit.statePath, 'utf8'));
344
+
345
+ assert.equal(state.audit.accepted_candidates.length, 0);
346
+ assert.equal(typeof state.audit.no_candidates_reason, 'string');
347
+ assert.match(await readFile(audit.reportPath, 'utf8'), /rejected candidates/i);
348
+ });
349
+ ```
350
+
351
+ - [ ] **Step 2: Run test to verify it fails**
352
+
353
+ Run: `node --test test/trellis-hardening.test.mjs -t "keeps finish audit explainable when no memory or spec candidates are accepted"`
354
+ Expected: FAIL until the audit report/state includes explicit no-candidate reasoning.
355
+
356
+ - [ ] **Step 3: Write minimal implementation**
357
+
358
+ Ensure `finish-audit` always emits:
359
+ - `inputs.scanned`
360
+ - `audit.accepted_candidates`
361
+ - `audit.rejected_candidates`
362
+ - `audit.no_candidates_reason`
363
+ - a report section that distinguishes accepted, rejected, and none cases
364
+
365
+ - [ ] **Step 4: Run test to verify it passes**
366
+
367
+ Run: `node --test test/trellis-hardening.test.mjs -t "keeps finish audit explainable when no memory or spec candidates are accepted"`
368
+ Expected: PASS
369
+
370
+ - [ ] **Step 5: Commit**
371
+
372
+ ```bash
373
+ git add src/finish-runtime.mjs test/trellis-hardening.test.mjs test/skill-governance.test.mjs
374
+ git commit -m "test: cover explainable finish audit none cases"
375
+ ```
376
+
377
+ ### Task 7: Final verification
378
+
379
+ **Files:**
380
+ - Test suite only unless failures require fixes.
381
+
382
+ - [ ] **Step 1: Run the targeted tests**
383
+
384
+ Run:
385
+ ```bash
386
+ node --test test/trellis-hardening.test.mjs
387
+ node --test test/skill-governance.test.mjs
388
+ ```
389
+ Expected: PASS
390
+
391
+ - [ ] **Step 2: Run the full suite**
392
+
393
+ Run: `npm test`
394
+ Expected: PASS
395
+
396
+ - [ ] **Step 3: Check packaging and docs alignment**
397
+
398
+ Run:
399
+ ```bash
400
+ npm pack --dry-run --json
401
+ node scripts/verify-skills.mjs
402
+ ```
403
+ Expected: PASS, with README command references and skill-mirror coverage aligned.
404
+
405
+ - [ ] **Step 4: Commit**
406
+
407
+ ```bash
408
+ git add src/finish-runtime.mjs src/cli.mjs README.md README.zh-CN.md skills/finish/SKILL.md plugins/loopx/skills/finish/SKILL.md test/trellis-hardening.test.mjs test/skill-governance.test.mjs scripts/verify-skills.mjs
409
+ git commit -m "feat: finish audit ledger and choice recording"
410
+ ```