@ai-content-space/loopx 0.2.4 → 0.2.8

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 (58) hide show
  1. package/README.md +108 -12
  2. package/README.zh-CN.md +109 -13
  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/design/loopx-skill-suite-v1-design.md +4 -4
  5. package/docs/loopx/memory/2026-06-09-stale-archive-hook-guidance.md +15 -0
  6. package/docs/loopx/memory/README.md +25 -0
  7. package/docs/loopx/plans/2026-06-08-finish-audit-change-window.md +933 -0
  8. package/docs/loopx/plans/2026-06-08-finish-learning-audit.md +410 -0
  9. package/docs/loopx/plans/2026-06-09-cli-onboarding-install-surface.md +1277 -0
  10. package/docs/loopx/plans/loopx-skill-suite-v1-implementation.md +1 -1
  11. package/docs/loopx/specs/installation.md +33 -0
  12. package/package.json +18 -2
  13. package/plugins/loopx/.codex-plugin/plugin.json +1 -1
  14. package/plugins/loopx/skills/clarify/SKILL.md +3 -3
  15. package/plugins/loopx/skills/debug/SKILL.md +1 -1
  16. package/plugins/loopx/skills/doc-readability/SKILL.md +1 -1
  17. package/plugins/loopx/skills/exec/SKILL.md +12 -2
  18. package/plugins/loopx/skills/final-review/SKILL.md +1 -1
  19. package/plugins/loopx/skills/finish/SKILL.md +39 -7
  20. package/plugins/loopx/skills/fix-review/SKILL.md +1 -1
  21. package/plugins/loopx/skills/go-style/SKILL.md +1 -1
  22. package/plugins/loopx/skills/kratos/SKILL.md +1 -1
  23. package/plugins/loopx/skills/{plan → plan-to-exec}/SKILL.md +5 -5
  24. package/plugins/loopx/skills/refactor-plan/SKILL.md +1 -1
  25. package/plugins/loopx/skills/review/SKILL.md +1 -1
  26. package/plugins/loopx/skills/spec/DESIGN_SPEC_TEMPLATE.md +2 -2
  27. package/plugins/loopx/skills/spec/SKILL.md +4 -4
  28. package/plugins/loopx/skills/subagent-exec/SKILL.md +14 -2
  29. package/plugins/loopx/skills/tdd/SKILL.md +1 -1
  30. package/plugins/loopx/skills/verify/SKILL.md +1 -1
  31. package/scripts/claude-workflow-hook.mjs +52 -3
  32. package/scripts/codex-workflow-hook.mjs +36 -15
  33. package/scripts/install-skills.mjs +58 -3
  34. package/scripts/verify-skills.mjs +83 -7
  35. package/skills/RESOLVER.md +4 -4
  36. package/skills/clarify/SKILL.md +3 -3
  37. package/skills/debug/SKILL.md +1 -1
  38. package/skills/doc-readability/SKILL.md +1 -1
  39. package/skills/exec/SKILL.md +12 -2
  40. package/skills/final-review/SKILL.md +1 -1
  41. package/skills/finish/SKILL.md +39 -7
  42. package/skills/fix-review/SKILL.md +1 -1
  43. package/skills/go-style/SKILL.md +1 -1
  44. package/skills/kratos/SKILL.md +1 -1
  45. package/skills/{plan → plan-to-exec}/SKILL.md +5 -5
  46. package/skills/refactor-plan/SKILL.md +1 -1
  47. package/skills/review/SKILL.md +1 -1
  48. package/skills/spec/DESIGN_SPEC_TEMPLATE.md +2 -2
  49. package/skills/spec/SKILL.md +4 -4
  50. package/skills/subagent-exec/SKILL.md +14 -2
  51. package/skills/tdd/SKILL.md +1 -1
  52. package/skills/verify/SKILL.md +1 -1
  53. package/src/cli.mjs +473 -86
  54. package/src/finish-runtime.mjs +1184 -0
  55. package/src/install-discovery.mjs +38 -1
  56. package/src/next-skill.mjs +10 -12
  57. package/src/workflow.mjs +21 -28
  58. package/skills/deepsearch/SKILL.md +0 -38
@@ -0,0 +1,1277 @@
1
+ # CLI Onboarding Install Surface 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:** Current conversation on 2026-06-09: approved product UX hardening for first-use CLI output, removal of `archive` from the public product flow, and installer/package surface tightening.
6
+
7
+ **Goal:** Make loopx friendlier for first-time users while keeping machine-readable output available through explicit `--json` flags and removing `archive` from the normal user-facing flow.
8
+
9
+ **Architecture:** Keep runtime behavior backwards-compatible where removing it would require a breaking archival subsystem migration, but remove `archive` from default help, generated guidance, hooks, and next-step hints. Reuse the existing `status` pattern: human output by default, `--json` for full state payloads. Add installer opt-out/dry-run/summary behavior without weakening existing conflict protection.
10
+
11
+ **Tech Stack:** Node.js ESM, built-in `node:test`, `node:assert/strict`, npm package file governance.
12
+
13
+ ---
14
+
15
+ ## File Structure
16
+
17
+ - Modify `src/cli.mjs`
18
+ - Add first-run help text.
19
+ - Add `--json` handling for `init`, `doctor`, and `install-skills`.
20
+ - Add human output helpers for init, doctor, and install command results.
21
+ - Keep the hidden `archive` command callable for compatibility, but remove it from default help.
22
+ - Modify `src/next-skill.mjs`
23
+ - Remove `$archive` from next skill recommendations.
24
+ - Return a concrete CLI approval command for review-approved workflows that still need `review -> done` approval.
25
+ - Modify `src/workflow.mjs`
26
+ - Remove `archive` from initialized workspace product surface metadata and generated `.loopx/README.md`.
27
+ - Change recommended next actions and Chinese review messages so they no longer instruct users to run archive.
28
+ - Keep `archiveStage` exported for compatibility with existing local runtime state and old tests unless a later breaking-change plan deletes it.
29
+ - Modify `scripts/codex-workflow-hook.mjs`
30
+ - Remove `$archive` hook suggestions so agent prompt advisories match the product flow.
31
+ - Modify `scripts/install-skills.mjs`
32
+ - Support `LOOPX_SKIP_POSTINSTALL=1` and `LOOPX_POSTINSTALL=0`.
33
+ - Print concise install summaries by default.
34
+ - Preserve JSON output when `--json` is passed.
35
+ - Modify `src/install-discovery.mjs`
36
+ - Export a target inspection helper for install dry-run output, or reuse `verifyInstallTargets` from callers without writing files.
37
+ - Do not bypass existing ownership/conflict checks for real installs.
38
+ - Modify `README.md` and `README.zh-CN.md`
39
+ - Document quickstart, human/JSON output split, hidden/deprecated archive relationship, postinstall opt-out, dry-run, written paths, repair, and hooks disablement.
40
+ - Modify `package.json`
41
+ - Replace broad `skills/` package inclusion with explicit bundled skill directories plus `skills/RESOLVER.md`.
42
+ - Modify `test/workflow.test.mjs`
43
+ - Update CLI help/doctor tests.
44
+ - Add human output and archive-removal regressions.
45
+ - Add installer dry-run and postinstall opt-out CLI/script tests.
46
+ - Modify `test/skill-governance.test.mjs`
47
+ - Assert npm package contains exactly bundled skill directories, not auxiliary skill sources such as `skills/deepsearch/`.
48
+ - Modify `scripts/verify-skills.mjs`
49
+ - Mirror package surface assertions for release governance.
50
+
51
+ ## Product Decisions
52
+
53
+ - `archive` is no longer part of the user-facing loopx product flow.
54
+ - The existing `archiveStage` implementation stays available as a hidden compatibility command for old runtime state. This avoids a large breaking deletion of spec-delta archival code in the same change.
55
+ - Default command output is for humans. Full JSON remains available with `--json`.
56
+ - `postinstall` remains convenient by default, but users get explicit opt-out and concise summaries.
57
+ - `install-skills --dry-run` never writes skills, hooks, lock files, settings, or template hashes.
58
+
59
+ ## Expected Human Output Contracts
60
+
61
+ `loopx --help` should start with:
62
+
63
+ ```text
64
+ Quick start:
65
+ loopx install-skills --target all --yes
66
+ loopx init --slug my-feature
67
+ loopx clarify my-feature
68
+ loopx status my-feature
69
+
70
+ Usage:
71
+ ```
72
+
73
+ `loopx init --slug demo` should print:
74
+
75
+ ```text
76
+ loopx workspace initialized
77
+ workspace: /abs/path/to/repo/.loopx
78
+ workflow: demo
79
+ stage: clarify
80
+ next: loopx clarify demo
81
+ details: loopx init --slug demo --json
82
+ ```
83
+
84
+ `loopx doctor` should print a compact status:
85
+
86
+ ```text
87
+ loopx doctor: attention needed
88
+ workspace: /abs/path/to/repo/.loopx
89
+ install: failed
90
+ conflicts: 2
91
+ fix:
92
+ loopx repair-install
93
+ details: loopx doctor --json
94
+ ```
95
+
96
+ `loopx install-skills --target codex --dry-run` should print:
97
+
98
+ ```text
99
+ loopx install-skills dry run
100
+ target: codex
101
+ skills: 16 bundled
102
+ writes: none
103
+ next: loopx install-skills --target codex --yes
104
+ ```
105
+
106
+ ---
107
+
108
+ ### Task 1: Add Human Defaults For Help, Init, And Doctor
109
+
110
+ **Files:**
111
+ - Modify: `src/cli.mjs`
112
+ - Test: `test/workflow.test.mjs`
113
+
114
+ - [ ] **Step 1: Write failing CLI tests for quickstart help and hidden archive**
115
+
116
+ In `test/workflow.test.mjs`, update the existing `CLI exposes loopx runtime/debug commands and no public team command` test near the bottom of the file.
117
+
118
+ Replace the help assertions with this block:
119
+
120
+ ```js
121
+ const { stdout: help } = await execFileAsync(process.execPath, [cliPath, '--help'], { cwd: repoRoot, env });
122
+ assert.match(help, /^Quick start:\n/m);
123
+ assert.match(help, /loopx install-skills --target all --yes/);
124
+ assert.match(help, /loopx init --slug my-feature/);
125
+ assert.match(help, /loopx clarify my-feature/);
126
+ assert.match(help, /loopx status my-feature/);
127
+ assert.match(help, /loopx repair-install/);
128
+ assert.match(help, /loopx plan \[slug\] \[--interactive\] \[--deliberate\]/);
129
+ assert.doesNotMatch(help, /--direct/);
130
+ assert.match(help, /loopx build <slug> \[--no-deslop\]/);
131
+ assert.match(help, /loopx build --from-review <review-report-path> \[--no-deslop\]/);
132
+ assert.doesNotMatch(help, /loopx archive <slug>/);
133
+ assert.doesNotMatch(help, /loopx team/);
134
+ ```
135
+
136
+ - [ ] **Step 2: Run the help test and confirm failure**
137
+
138
+ Run:
139
+
140
+ ```bash
141
+ node --test --test-name-pattern "CLI exposes loopx runtime/debug commands and no public team command" test/workflow.test.mjs
142
+ ```
143
+
144
+ Expected: fail because help does not contain `Quick start:` and still exposes `loopx archive <slug>`.
145
+
146
+ - [ ] **Step 3: Update `usage()`**
147
+
148
+ In `src/cli.mjs`, replace the existing `usage()` body with:
149
+
150
+ ```js
151
+ function usage() {
152
+ return [
153
+ 'Quick start:',
154
+ ' loopx install-skills --target all --yes',
155
+ ' loopx init --slug my-feature',
156
+ ' loopx clarify my-feature',
157
+ ' loopx status my-feature',
158
+ '',
159
+ 'Usage:',
160
+ ' loopx --version',
161
+ ' loopx init [--slug <slug>] [--enable-agent-delegation] [--auto-agent-delegation] [--agent-delegation-threshold <local|critic-only|parallel-review>] [--json]',
162
+ ' loopx clarify <slug> [--standard|--deep]',
163
+ ' loopx approve <slug> --from <stage> --to <stage>',
164
+ ' loopx plan [slug] [--interactive] [--deliberate]',
165
+ ' loopx build <slug> [--no-deslop]',
166
+ ' loopx build --from-review <review-report-path> [--no-deslop]',
167
+ ' loopx review <slug> [--reviewer <name>]',
168
+ ' loopx autopilot <slug> [--reviewer <name>]',
169
+ ' loopx finish-start [slug] [--source <path>] [--json]',
170
+ ' loopx finish-audit [slug] [--baseline <git-ref>] [--json]',
171
+ ' loopx finish-record <audit-id-or-path> --action <merge|pr|keep|discard> --status <pending|done|failed|aborted> [--summary <text>] [--url <url>]',
172
+ ' loopx render [slug|--all]',
173
+ ' loopx status [slug] [--json]',
174
+ ' loopx setup-context',
175
+ ' loopx install-skills [--target <codex|claude|all>] [--project] [--mode <copy|symlink>] [--dir <path>] [--yes] [--dry-run] [--json]',
176
+ ' loopx doctor [--json]',
177
+ ' loopx migrate',
178
+ ' loopx repair-install',
179
+ ].join('\n');
180
+ }
181
+ ```
182
+
183
+ - [ ] **Step 4: Write failing init output tests**
184
+
185
+ In `test/workflow.test.mjs`, add this test after the CLI help test or near other CLI tests:
186
+
187
+ ```js
188
+ it('prints human init output by default and full init state with --json', async () => {
189
+ const wd = await mkdtemp(join(tmpdir(), 'loopx-cli-init-human-'));
190
+ const home = await mkdtemp(join(tmpdir(), 'loopx-cli-init-home-'));
191
+ const env = loopxEnv(home);
192
+
193
+ const { stdout: human } = await execFileAsync(process.execPath, [cliPath, 'init', '--slug', 'demo-flow'], { cwd: wd, env });
194
+ assert.match(human, /^loopx workspace initialized$/m);
195
+ assert.match(human, /workflow: demo-flow/);
196
+ assert.match(human, /stage: clarify/);
197
+ assert.match(human, /next: loopx clarify demo-flow/);
198
+ assert.match(human, /details: loopx init --slug demo-flow --json/);
199
+ assert.throws(() => JSON.parse(human));
200
+
201
+ const { stdout: json } = await execFileAsync(process.execPath, [cliPath, 'init', '--slug', 'json-flow', '--json'], { cwd: wd, env });
202
+ const parsed = JSON.parse(json);
203
+ assert.equal(parsed.ok, true);
204
+ assert.equal(parsed.command, 'init');
205
+ assert.equal(parsed.workflow.slug, 'json-flow');
206
+ assert.equal(parsed.workflow.current_stage, 'clarify');
207
+ });
208
+ ```
209
+
210
+ - [ ] **Step 5: Run the init output test and confirm failure**
211
+
212
+ Run:
213
+
214
+ ```bash
215
+ node --test --test-name-pattern "prints human init output" test/workflow.test.mjs
216
+ ```
217
+
218
+ Expected: fail because default `loopx init` currently prints JSON and `--json` is not handled explicitly.
219
+
220
+ - [ ] **Step 6: Add human init printer**
221
+
222
+ In `src/cli.mjs`, add this helper after `printHumanStatus`:
223
+
224
+ ```js
225
+ function printHumanInit(result, options = new Map()) {
226
+ const workflow = result.workflow?.state ?? null;
227
+ console.log('loopx workspace initialized');
228
+ console.log(`workspace: ${result.workspaceRoot}`);
229
+ if (!workflow) {
230
+ console.log('workflow: (none)');
231
+ console.log('next: loopx clarify <slug>');
232
+ console.log('details: loopx init --json');
233
+ return;
234
+ }
235
+ console.log(`workflow: ${workflow.slug}`);
236
+ console.log(`stage: ${workflow.current_stage ?? '(none)'}`);
237
+ console.log(`next: loopx clarify ${workflow.slug}`);
238
+ const slug = options.get('--slug') || workflow.slug;
239
+ console.log(`details: loopx init --slug ${slug} --json`);
240
+ }
241
+ ```
242
+
243
+ - [ ] **Step 7: Change `init` command output**
244
+
245
+ In the `case 'init'` block in `src/cli.mjs`, replace the final `console.log(JSON.stringify(...))` with:
246
+
247
+ ```js
248
+ if (options.get('--json')) {
249
+ console.log(JSON.stringify({ ok: true, command, workspaceRoot: result.workspaceRoot, workflow: result.workflow?.state ?? null }, null, 2));
250
+ } else {
251
+ printHumanInit(result, options);
252
+ }
253
+ ```
254
+
255
+ - [ ] **Step 8: Write failing doctor output tests**
256
+
257
+ In the existing CLI help test in `test/workflow.test.mjs`, replace the doctor JSON checks with:
258
+
259
+ ```js
260
+ const { stdout: doctor } = await execFileAsync(process.execPath, [cliPath, 'doctor'], { cwd: repoRoot, env });
261
+ assert.match(doctor, /^loopx doctor: attention needed$/m);
262
+ assert.match(doctor, /install: failed/);
263
+ assert.match(doctor, /fix:/);
264
+ assert.match(doctor, /loopx repair-install/);
265
+ assert.match(doctor, /details: loopx doctor --json/);
266
+ assert.throws(() => JSON.parse(doctor));
267
+
268
+ const { stdout: doctorJson } = await execFileAsync(process.execPath, [cliPath, 'doctor', '--json'], { cwd: repoRoot, env });
269
+ const parsedDoctor = JSON.parse(doctorJson);
270
+ assert.equal(parsedDoctor.command, 'doctor');
271
+
272
+ await execFileAsync(process.execPath, [cliPath, 'repair-install'], { cwd: repoRoot, env });
273
+ const { stdout: afterDoctor } = await execFileAsync(process.execPath, [cliPath, 'doctor', '--json'], { cwd: repoRoot, env });
274
+ const parsedAfterDoctor = JSON.parse(afterDoctor);
275
+ assert.equal(parsedAfterDoctor.installCheck.ok, true);
276
+ ```
277
+
278
+ - [ ] **Step 9: Run the doctor output test and confirm failure**
279
+
280
+ Run:
281
+
282
+ ```bash
283
+ node --test --test-name-pattern "CLI exposes loopx runtime/debug commands and no public team command" test/workflow.test.mjs
284
+ ```
285
+
286
+ Expected: fail because `loopx doctor` currently prints JSON by default and `loopx doctor --json` is not a distinct mode.
287
+
288
+ - [ ] **Step 10: Add doctor summary helpers**
289
+
290
+ In `src/cli.mjs`, add these helpers after `printHumanInit`:
291
+
292
+ ```js
293
+ function countInstallConflicts(result) {
294
+ return Object.values(result.installCheck?.results || {})
295
+ .reduce((sum, target) => sum + (Array.isArray(target.conflicts) ? target.conflicts.length : 0), 0);
296
+ }
297
+
298
+ function printHumanDoctor(result) {
299
+ const ok = !result.mixedRuntimeRoots && result.installCheck?.ok === true;
300
+ console.log(`loopx doctor: ${ok ? 'ok' : 'attention needed'}`);
301
+ console.log(`workspace: ${result.loopxRoot ?? result.workspaceRoot ?? '(unknown)'}`);
302
+ if (result.mixedRuntimeRoots) {
303
+ console.log('runtime roots: mixed .loopx and .LoopX detected');
304
+ } else {
305
+ console.log('runtime roots: ok');
306
+ }
307
+ console.log(`install: ${result.installCheck?.ok === true ? 'ok' : 'failed'}`);
308
+ const conflicts = countInstallConflicts(result);
309
+ if (conflicts > 0) {
310
+ console.log(`conflicts: ${conflicts}`);
311
+ }
312
+ if (result.hook) {
313
+ console.log(`hooks: ${result.hook.enabled ? 'enabled' : 'disabled'}`);
314
+ }
315
+ if (!ok) {
316
+ console.log('fix:');
317
+ console.log(' loopx repair-install');
318
+ console.log(' LOOPX_HOOKS=0 disables loopx hooks for the current process');
319
+ }
320
+ console.log('details: loopx doctor --json');
321
+ }
322
+ ```
323
+
324
+ - [ ] **Step 11: Change `doctor` command output**
325
+
326
+ In the `case 'doctor'` block in `src/cli.mjs`, replace the `console.log(JSON.stringify(...))` line with:
327
+
328
+ ```js
329
+ const payload = { ok: !result.mixedRuntimeRoots && result.installCheck.ok, command, ...result };
330
+ if (options.get('--json')) {
331
+ console.log(JSON.stringify(payload, null, 2));
332
+ } else {
333
+ printHumanDoctor(payload);
334
+ }
335
+ ```
336
+
337
+ - [ ] **Step 12: Run task tests**
338
+
339
+ Run:
340
+
341
+ ```bash
342
+ node --test --test-name-pattern "prints human init output|CLI exposes loopx runtime/debug commands and no public team command" test/workflow.test.mjs
343
+ ```
344
+
345
+ Expected: both tests pass.
346
+
347
+ - [ ] **Step 13: Commit**
348
+
349
+ ```bash
350
+ git add src/cli.mjs test/workflow.test.mjs
351
+ git commit -m "feat: add human cli onboarding output"
352
+ ```
353
+
354
+ ---
355
+
356
+ ### Task 2: Remove Archive From Product Guidance
357
+
358
+ **Files:**
359
+ - Modify: `src/next-skill.mjs`
360
+ - Modify: `src/workflow.mjs`
361
+ - Modify: `scripts/codex-workflow-hook.mjs`
362
+ - Modify: `test/workflow.test.mjs`
363
+
364
+ - [ ] **Step 1: Replace the archive next-skill regression test**
365
+
366
+ In `test/workflow.test.mjs`, replace the test named `CLI payload adds the archive skill command after done approval` with this test:
367
+
368
+ ```js
369
+ it('does not recommend archive after approved review or done approval', async () => {
370
+ const wd = await mkdtemp(join(tmpdir(), 'loopx-no-archive-cli-next-'));
371
+ const clarified = await clarifyStage(wd, 'no-archive-cli-next');
372
+ await writeResolvedSpec(clarified.root, 'no-archive-cli-next');
373
+ await approveStage(wd, 'no-archive-cli-next', { from: 'clarify', to: 'plan' });
374
+ await planStage(wd, 'no-archive-cli-next', { adapter: createScriptedPlanAdapter() });
375
+ await approveStage(wd, 'no-archive-cli-next', { from: 'plan', to: 'build' });
376
+ await buildStage(wd, 'no-archive-cli-next', {
377
+ adapter: createScriptedBuildAdapter(),
378
+ });
379
+ await approveStage(wd, 'no-archive-cli-next', { from: 'build', to: 'review' });
380
+ await reviewStage(wd, 'no-archive-cli-next', {
381
+ adapter: createScriptedReviewAdapter({ verdict: 'approve' }),
382
+ });
383
+ const reviewed = await readState(wd, 'no-archive-cli-next');
384
+ const reviewPayload = withNextSkill({ ok: true }, reviewed);
385
+ assert.equal(reviewPayload.next_skill_command, null);
386
+ assert.equal(reviewPayload.next_skill_hint, null);
387
+ assert.equal(reviewPayload.next_cli_command, 'loopx approve no-archive-cli-next --from review --to done');
388
+ assert.equal(reviewPayload.next_cli_hint, 'Next CLI: loopx approve no-archive-cli-next --from review --to done');
389
+
390
+ const done = await approveStage(wd, 'no-archive-cli-next', { from: 'review', to: 'done' });
391
+ const payload = withNextSkill({ ok: true }, done.state);
392
+ assert.equal(payload.next_skill_command, '$finish');
393
+ assert.equal(payload.next_skill_hint, 'Next skill: $finish');
394
+ assert.equal(payload.next_cli_command, null);
395
+ assert.equal(payload.next_cli_hint, null);
396
+ });
397
+ ```
398
+
399
+ - [ ] **Step 2: Run the next-skill test and confirm failure**
400
+
401
+ Run:
402
+
403
+ ```bash
404
+ node --test --test-name-pattern "does not recommend archive" test/workflow.test.mjs
405
+ ```
406
+
407
+ Expected: fail because the current code returns `$archive <slug>` after review approval and done approval.
408
+
409
+ - [ ] **Step 3: Update next-step logic**
410
+
411
+ In `src/next-skill.mjs`, delete both branches that return `$archive ${state.slug}`.
412
+
413
+ Add this branch before the existing request-changes review branches in `nextCliCommand`:
414
+
415
+ ```js
416
+ if (state.current_stage === 'review'
417
+ && state.review_verdict === 'approve'
418
+ && state.pending_user_decision === 'review->done'
419
+ && ['requested', 'approved'].includes(state.approval?.complete)) {
420
+ return `loopx approve ${state.slug} --from review --to done`;
421
+ }
422
+ ```
423
+
424
+ Add this branch near the start of `nextSkillCommand`, after the clarify-ready branch:
425
+
426
+ ```js
427
+ if (state.current_stage === 'done'
428
+ && state.completion_confirmed === true) {
429
+ return '$finish';
430
+ }
431
+ ```
432
+
433
+ - [ ] **Step 4: Update recommended actions in workflow runtime**
434
+
435
+ In `src/workflow.mjs`, update `recommendedAction(state, legacy = false)`:
436
+
437
+ Replace the `state.review_verdict === 'approve'` branch with:
438
+
439
+ ```js
440
+ if (state.review_verdict === 'approve') {
441
+ return state.approval.complete === APPROVAL_STATES.APPROVED
442
+ ? 'Run $finish to complete branch disposition and learning audit.'
443
+ : 'Approve review -> done, then run $finish to complete branch disposition and learning audit.';
444
+ }
445
+ ```
446
+
447
+ Replace the `STAGES.DONE` archive check with:
448
+
449
+ ```js
450
+ return 'Workflow is complete. Run $finish if branch disposition and learning audit have not been recorded.';
451
+ ```
452
+
453
+ - [ ] **Step 5: Update review user message copy**
454
+
455
+ In `src/workflow.mjs`, update `nextCommandForRollbackTarget(slug, target)` for `target === 'none'`:
456
+
457
+ ```js
458
+ if (target === 'none') {
459
+ return [
460
+ 'Next:',
461
+ `loopx approve ${slug} --from review --to done`,
462
+ '$finish',
463
+ ].join('\n');
464
+ }
465
+ ```
466
+
467
+ In `reviewUserMessageZh`, replace the approve text:
468
+
469
+ ```js
470
+ const next = verdict === 'APPROVE'
471
+ ? `下一步:批准 review -> done,然后执行 finish 完成分支处置和学习审计。\n${nextCommandForRollbackTarget(slug, 'none')}`
472
+ : `下一步:按审查发现处理,并${rollbackTargetLabel(rollbackTarget)}。\n${nextCommandForRollbackTarget(slug, rollbackTarget)}`;
473
+ ```
474
+
475
+ - [ ] **Step 6: Update initialized workspace product surface**
476
+
477
+ In `src/workflow.mjs`, update `buildWorkspaceReadme()`:
478
+
479
+ Remove this runtime command line:
480
+
481
+ ```js
482
+ '- `loopx archive <slug>`',
483
+ ```
484
+
485
+ In `initWorkspace`, update the config object:
486
+
487
+ ```js
488
+ default_flow: ['clarify', 'plan', 'build', 'review', 'done'],
489
+ preferred_surface: ['clarify', 'plan', 'build', 'review', 'autopilot'],
490
+ ```
491
+
492
+ - [ ] **Step 7: Update hook advisory logic**
493
+
494
+ In `scripts/codex-workflow-hook.mjs`, remove both `nextSkill(state)` branches that return `$archive ${state.slug}`.
495
+
496
+ Add this branch after the clarify-ready branch:
497
+
498
+ ```js
499
+ if (state.current_stage === 'done'
500
+ && state.completion_confirmed === true) {
501
+ return '$finish';
502
+ }
503
+ ```
504
+
505
+ Update `nextCli(state)` with the same review-approved branch used in `src/next-skill.mjs`:
506
+
507
+ ```js
508
+ if (state.current_stage === 'review'
509
+ && state.review_verdict === 'approve'
510
+ && state.pending_user_decision === 'review->done'
511
+ && ['requested', 'approved'].includes(state.approval?.complete)) {
512
+ return `loopx approve ${state.slug} --from review --to done`;
513
+ }
514
+ ```
515
+
516
+ - [ ] **Step 8: Add workspace README/config regression**
517
+
518
+ In `test/workflow.test.mjs`, add this test near the init/status tests:
519
+
520
+ ```js
521
+ it('initializes workspace metadata without archive in the preferred product flow', async () => {
522
+ const wd = await mkdtemp(join(tmpdir(), 'loopx-init-no-archive-'));
523
+ await initWorkspace(wd);
524
+
525
+ const workspaceRoot = resolveWorkspaceRoot(wd);
526
+ const config = JSON.parse(await readFile(join(workspaceRoot, 'config.json'), 'utf8'));
527
+ assert.deepEqual(config.default_flow, ['clarify', 'plan', 'build', 'review', 'done']);
528
+ assert.deepEqual(config.preferred_surface, ['clarify', 'plan', 'build', 'review', 'autopilot']);
529
+
530
+ const readme = await readFile(join(workspaceRoot, 'README.md'), 'utf8');
531
+ assert.match(readme, /clarify -> plan -> build -> review -> done/);
532
+ assert.doesNotMatch(readme, /loopx archive/);
533
+ });
534
+ ```
535
+
536
+ - [ ] **Step 9: Run archive guidance tests**
537
+
538
+ Run:
539
+
540
+ ```bash
541
+ node --test --test-name-pattern "does not recommend archive|initializes workspace metadata without archive" test/workflow.test.mjs
542
+ ```
543
+
544
+ Expected: both tests pass.
545
+
546
+ - [ ] **Step 10: Commit**
547
+
548
+ ```bash
549
+ git add src/next-skill.mjs src/workflow.mjs scripts/codex-workflow-hook.mjs test/workflow.test.mjs
550
+ git commit -m "feat: remove archive from product guidance"
551
+ ```
552
+
553
+ ---
554
+
555
+ ### Task 3: Add Installer Dry-Run, Opt-Out, And Human Summary
556
+
557
+ **Files:**
558
+ - Modify: `src/cli.mjs`
559
+ - Modify: `src/install-discovery.mjs`
560
+ - Modify: `scripts/install-skills.mjs`
561
+ - Test: `test/workflow.test.mjs`
562
+
563
+ - [ ] **Step 1: Write failing CLI dry-run test**
564
+
565
+ In `test/workflow.test.mjs`, add this test near the install tests:
566
+
567
+ ```js
568
+ it('prints install dry-run summary without writing skills or hooks', async () => {
569
+ const home = await mkdtemp(join(tmpdir(), 'loopx-install-dry-run-home-'));
570
+ const env = loopxEnv(home);
571
+
572
+ const { stdout } = await execFileAsync(process.execPath, [
573
+ cliPath,
574
+ 'install-skills',
575
+ '--target',
576
+ 'codex',
577
+ '--dry-run',
578
+ ], { cwd: repoRoot, env });
579
+
580
+ assert.match(stdout, /^loopx install-skills dry run$/m);
581
+ assert.match(stdout, /target: codex/);
582
+ assert.match(stdout, /writes: none/);
583
+ assert.match(stdout, /next: loopx install-skills --target codex --yes/);
584
+ assert.equal(existsSync(join(home, '.agents', 'skills')), false);
585
+ assert.equal(existsSync(join(home, '.codex', 'hooks', 'codex-workflow-hook.mjs')), false);
586
+
587
+ const { stdout: json } = await execFileAsync(process.execPath, [
588
+ cliPath,
589
+ 'install-skills',
590
+ '--target',
591
+ 'codex',
592
+ '--dry-run',
593
+ '--json',
594
+ ], { cwd: repoRoot, env });
595
+ const parsed = JSON.parse(json);
596
+ assert.equal(parsed.ok, true);
597
+ assert.equal(parsed.command, 'install-skills');
598
+ assert.equal(parsed.dryRun, true);
599
+ assert.deepEqual(parsed.targets, ['codex']);
600
+ });
601
+ ```
602
+
603
+ - [ ] **Step 2: Run dry-run test and confirm failure**
604
+
605
+ Run:
606
+
607
+ ```bash
608
+ node --test --test-name-pattern "prints install dry-run summary" test/workflow.test.mjs
609
+ ```
610
+
611
+ Expected: fail because `--dry-run` is not implemented.
612
+
613
+ - [ ] **Step 3: Export target inspection helper**
614
+
615
+ In `src/install-discovery.mjs`, add this function before `installSkillsForTargets`:
616
+
617
+ ```js
618
+ export async function inspectInstallTargets(env = process.env, options = {}) {
619
+ const requestedTargets = Array.isArray(options.targets) && options.targets.length > 0
620
+ ? options.targets
621
+ : ['codex', 'claude'];
622
+ const results = {};
623
+ for (const target of requestedTargets) {
624
+ if (target === 'codex') {
625
+ results.codex = await inspectInstallState(codexInstallEnv({
626
+ ...env,
627
+ LOOPX_INSTALL_CUSTOM_DIR: options.dir,
628
+ }));
629
+ continue;
630
+ }
631
+ if (target === 'claude') {
632
+ results.claude = await inspectInstallState(claudeInstallEnv(env, options));
633
+ continue;
634
+ }
635
+ throw new Error(`unknown_install_target:${target}`);
636
+ }
637
+ return {
638
+ ok: true,
639
+ dryRun: true,
640
+ targets: requestedTargets,
641
+ results,
642
+ };
643
+ }
644
+ ```
645
+
646
+ - [ ] **Step 4: Import inspection helper in CLI**
647
+
648
+ In `src/cli.mjs`, change the install import line:
649
+
650
+ ```js
651
+ import { inspectInstallTargets, installBundledSkills, installSkillsForTargets } from './install-discovery.mjs';
652
+ ```
653
+
654
+ If `installBundledSkills` is unused after this change, remove it from the import list:
655
+
656
+ ```js
657
+ import { inspectInstallTargets, installSkillsForTargets } from './install-discovery.mjs';
658
+ ```
659
+
660
+ - [ ] **Step 5: Add install summary helpers**
661
+
662
+ In `src/cli.mjs`, add these helpers after `printHumanDoctor`:
663
+
664
+ ```js
665
+ function installTargetNames(result) {
666
+ return Array.isArray(result.targets) && result.targets.length > 0 ? result.targets : Object.keys(result.results || {});
667
+ }
668
+
669
+ function countInstalledSkills(result) {
670
+ return Object.values(result.results || {})
671
+ .reduce((sum, target) => sum + (Array.isArray(target.installed) ? target.installed.length : 0), 0);
672
+ }
673
+
674
+ function countInstallSkipped(result) {
675
+ return Object.values(result.results || {})
676
+ .reduce((sum, target) => sum + (Array.isArray(target.skipped) ? target.skipped.length : 0), 0);
677
+ }
678
+
679
+ function printHumanInstall(result, { dryRun = false } = {}) {
680
+ if (dryRun) {
681
+ console.log('loopx install-skills dry run');
682
+ for (const target of installTargetNames(result)) {
683
+ console.log(`target: ${target}`);
684
+ }
685
+ console.log('skills: 16 bundled');
686
+ console.log('writes: none');
687
+ console.log(`next: loopx install-skills --target ${installTargetNames(result).join(',')} --yes`);
688
+ return;
689
+ }
690
+
691
+ console.log(`loopx install-skills: ${result.ok === false ? 'attention needed' : 'ok'}`);
692
+ console.log(`targets: ${installTargetNames(result).join(', ')}`);
693
+ console.log(`installed skills: ${countInstalledSkills(result)}`);
694
+ const conflicts = countInstallConflicts({ installCheck: result });
695
+ console.log(`conflicts: ${conflicts}`);
696
+ const skipped = countInstallSkipped(result);
697
+ if (skipped > 0) {
698
+ console.log(`skipped user-modified: ${skipped}`);
699
+ }
700
+ console.log('paths:');
701
+ for (const target of installTargetNames(result)) {
702
+ const inspection = result.results?.[target]?.inspection || result.results?.[target];
703
+ if (inspection?.installedSkillsRoot) {
704
+ console.log(` ${target} skills: ${inspection.installedSkillsRoot}`);
705
+ }
706
+ }
707
+ console.log('repair: loopx repair-install');
708
+ console.log('disable hooks for one process: LOOPX_HOOKS=0');
709
+ console.log('details: loopx install-skills --json');
710
+ }
711
+ ```
712
+
713
+ - [ ] **Step 6: Wire install command dry-run and JSON output**
714
+
715
+ In `src/cli.mjs`, in `case 'install-skills'`, replace the install execution/output block with:
716
+
717
+ ```js
718
+ const env = {
719
+ ...process.env,
720
+ LOOPX_INSTALL_CWD: process.cwd(),
721
+ };
722
+ const result = options.get('--dry-run')
723
+ ? await inspectInstallTargets(env, installOptions)
724
+ : await installSkillsForTargets(env, installOptions);
725
+ const payload = { ok: result.ok, command, ...result };
726
+ if (options.get('--json')) {
727
+ console.log(JSON.stringify(payload, null, 2));
728
+ } else {
729
+ printHumanInstall(payload, { dryRun: Boolean(options.get('--dry-run')) });
730
+ }
731
+ return;
732
+ ```
733
+
734
+ - [ ] **Step 7: Write failing postinstall opt-out test**
735
+
736
+ In `test/workflow.test.mjs`, add:
737
+
738
+ ```js
739
+ it('lets postinstall opt out without writing user-level skills or hooks', async () => {
740
+ const home = await mkdtemp(join(tmpdir(), 'loopx-postinstall-skip-home-'));
741
+ const env = {
742
+ ...loopxEnv(home),
743
+ LOOPX_SKIP_POSTINSTALL: '1',
744
+ };
745
+
746
+ const { stdout } = await execFileAsync(process.execPath, [installScript], { cwd: repoRoot, env });
747
+ assert.match(stdout, /loopx postinstall skipped/);
748
+ assert.match(stdout, /LOOPX_SKIP_POSTINSTALL=1/);
749
+ assert.equal(existsSync(join(home, '.agents', 'skills')), false);
750
+ assert.equal(existsSync(join(home, '.claude', 'skills')), false);
751
+ assert.equal(existsSync(join(home, '.codex', 'hooks', 'codex-workflow-hook.mjs')), false);
752
+ });
753
+ ```
754
+
755
+ - [ ] **Step 8: Run opt-out test and confirm failure**
756
+
757
+ Run:
758
+
759
+ ```bash
760
+ node --test --test-name-pattern "lets postinstall opt out" test/workflow.test.mjs
761
+ ```
762
+
763
+ Expected: fail because `scripts/install-skills.mjs` does not handle `LOOPX_SKIP_POSTINSTALL`.
764
+
765
+ - [ ] **Step 9: Update postinstall script**
766
+
767
+ In `scripts/install-skills.mjs`, replace the file with this implementation:
768
+
769
+ ```js
770
+ #!/usr/bin/env node
771
+
772
+ import { installSkillsForTargets, verifyInstallTargets } from '../src/install-discovery.mjs';
773
+
774
+ function shouldSkipPostinstall(env = process.env) {
775
+ return env.LOOPX_SKIP_POSTINSTALL === '1' || env.LOOPX_POSTINSTALL === '0';
776
+ }
777
+
778
+ function targetNames(result) {
779
+ return Array.isArray(result.targets) && result.targets.length > 0 ? result.targets : Object.keys(result.results || {});
780
+ }
781
+
782
+ function count(result, key) {
783
+ return Object.values(result.results || {})
784
+ .reduce((sum, target) => sum + (Array.isArray(target?.[key]) ? target[key].length : 0), 0);
785
+ }
786
+
787
+ function printSummary(result, { checkOnly = false } = {}) {
788
+ console.log(`loopx ${checkOnly ? 'install check' : 'postinstall'}: ${result.ok === false ? 'attention needed' : 'ok'}`);
789
+ console.log(`targets: ${targetNames(result).join(', ')}`);
790
+ if (!checkOnly) {
791
+ console.log(`installed skills: ${count(result, 'installed')}`);
792
+ }
793
+ console.log(`conflicts: ${count(result, 'conflicts')}`);
794
+ console.log(`skipped user-modified: ${count(result, 'skipped')}`);
795
+ console.log('repair: loopx repair-install');
796
+ console.log('opt out: LOOPX_SKIP_POSTINSTALL=1');
797
+ console.log('disable hooks for one process: LOOPX_HOOKS=0');
798
+ console.log('details: node scripts/install-skills.mjs --json');
799
+ }
800
+
801
+ async function main() {
802
+ if (shouldSkipPostinstall()) {
803
+ console.log('loopx postinstall skipped: LOOPX_SKIP_POSTINSTALL=1 or LOOPX_POSTINSTALL=0');
804
+ return;
805
+ }
806
+
807
+ const checkOnly = process.argv.includes('--check');
808
+ const json = process.argv.includes('--json');
809
+ const result = checkOnly ? await verifyInstallTargets(process.env) : await installSkillsForTargets(process.env);
810
+ const ok = checkOnly ? result.ok : result.ok !== false;
811
+ const payload = checkOnly ? result : { ok, targets: result.targets, results: result.results };
812
+ if (json) {
813
+ const stream = ok ? process.stdout : process.stderr;
814
+ stream.write(`${JSON.stringify(payload, null, 2)}\n`);
815
+ } else {
816
+ printSummary(payload, { checkOnly });
817
+ }
818
+ if (!ok) {
819
+ process.exitCode = 1;
820
+ }
821
+ }
822
+
823
+ await main();
824
+ ```
825
+
826
+ - [ ] **Step 10: Run installer tests**
827
+
828
+ Run:
829
+
830
+ ```bash
831
+ node --test --test-name-pattern "prints install dry-run summary|lets postinstall opt out" test/workflow.test.mjs
832
+ ```
833
+
834
+ Expected: both tests pass.
835
+
836
+ - [ ] **Step 11: Commit**
837
+
838
+ ```bash
839
+ git add src/cli.mjs src/install-discovery.mjs scripts/install-skills.mjs test/workflow.test.mjs
840
+ git commit -m "feat: improve install summary and dry run"
841
+ ```
842
+
843
+ ---
844
+
845
+ ### Task 4: Document The New UX And Archive Relationship
846
+
847
+ **Files:**
848
+ - Modify: `README.md`
849
+ - Modify: `README.zh-CN.md`
850
+ - Modify: `test/skill-governance.test.mjs`
851
+ - Modify: `scripts/verify-skills.mjs`
852
+
853
+ - [ ] **Step 1: Write failing docs alignment assertions**
854
+
855
+ In `test/skill-governance.test.mjs`, inside `keeps public docs structurally valid and bilingual release docs aligned`, add these required strings after the existing required docs checks:
856
+
857
+ ```js
858
+ for (const required of [
859
+ 'Quick start',
860
+ 'Human output is the default',
861
+ 'loopx doctor --json',
862
+ 'loopx init --json',
863
+ 'loopx install-skills --dry-run',
864
+ 'LOOPX_SKIP_POSTINSTALL=1',
865
+ 'LOOPX_POSTINSTALL=0',
866
+ 'LOOPX_HOOKS=0',
867
+ 'Archive compatibility',
868
+ 'archive is not part of the public v1 finish flow',
869
+ ]) {
870
+ assert.match(readme, new RegExp(required.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), `${required} missing from README.md`);
871
+ }
872
+ for (const required of [
873
+ '快速开始',
874
+ '默认输出面向人类',
875
+ 'loopx doctor --json',
876
+ 'loopx init --json',
877
+ 'loopx install-skills --dry-run',
878
+ 'LOOPX_SKIP_POSTINSTALL=1',
879
+ 'LOOPX_POSTINSTALL=0',
880
+ 'LOOPX_HOOKS=0',
881
+ 'Archive 兼容性',
882
+ 'archive 不属于公开 v1 finish 流程',
883
+ ]) {
884
+ assert.match(readmeZh, new RegExp(required.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), `${required} missing from README.zh-CN.md`);
885
+ }
886
+ ```
887
+
888
+ In `scripts/verify-skills.mjs`, in `assertPublicDocsAligned()`, add:
889
+
890
+ ```js
891
+ for (const required of [
892
+ 'Quick start',
893
+ 'Human output is the default',
894
+ 'loopx install-skills --dry-run',
895
+ 'LOOPX_SKIP_POSTINSTALL=1',
896
+ 'LOOPX_POSTINSTALL=0',
897
+ 'LOOPX_HOOKS=0',
898
+ 'Archive compatibility',
899
+ ]) {
900
+ assertContains(readme, required, 'README.md');
901
+ }
902
+ for (const required of [
903
+ '快速开始',
904
+ '默认输出面向人类',
905
+ 'loopx install-skills --dry-run',
906
+ 'LOOPX_SKIP_POSTINSTALL=1',
907
+ 'LOOPX_POSTINSTALL=0',
908
+ 'LOOPX_HOOKS=0',
909
+ 'Archive 兼容性',
910
+ ]) {
911
+ assertContains(readmeZh, required, 'README.zh-CN.md');
912
+ }
913
+ ```
914
+
915
+ - [ ] **Step 2: Run docs tests and confirm failure**
916
+
917
+ Run:
918
+
919
+ ```bash
920
+ node --test --test-name-pattern "keeps public docs structurally valid" test/skill-governance.test.mjs
921
+ node scripts/verify-skills.mjs
922
+ ```
923
+
924
+ Expected: fail because README files do not yet document quickstart, human defaults, installer opt-out, dry-run, and archive compatibility.
925
+
926
+ - [ ] **Step 3: Update English README quickstart and CLI behavior**
927
+
928
+ In `README.md`, after the recommended v1 flow block, add:
929
+
930
+ ````markdown
931
+ ## Quick start
932
+
933
+ ```bash
934
+ loopx install-skills --target all --yes
935
+ loopx init --slug my-feature
936
+ loopx clarify my-feature
937
+ loopx status my-feature
938
+ ```
939
+
940
+ Human output is the default for first-use commands such as `loopx init`, `loopx doctor`, and `loopx install-skills`. Use `--json` when an agent or script needs the complete runtime payload:
941
+
942
+ ```bash
943
+ loopx init --slug my-feature --json
944
+ loopx doctor --json
945
+ loopx install-skills --target all --json
946
+ ```
947
+ ````
948
+
949
+ - [ ] **Step 4: Update English README install section**
950
+
951
+ In `README.md`, in the install section after the path list, add:
952
+
953
+ ````markdown
954
+ To inspect without writing files:
955
+
956
+ ```bash
957
+ loopx install-skills --target all --dry-run
958
+ ```
959
+
960
+ To opt out during npm postinstall:
961
+
962
+ ```bash
963
+ LOOPX_SKIP_POSTINSTALL=1 npm install -g @ai-content-space/loopx
964
+ LOOPX_POSTINSTALL=0 npm install -g @ai-content-space/loopx
965
+ ```
966
+
967
+ To disable loopx hooks for one process:
968
+
969
+ ```bash
970
+ LOOPX_HOOKS=0 codex
971
+ ```
972
+
973
+ Repair an interrupted or conflicted install with:
974
+
975
+ ```bash
976
+ loopx repair-install
977
+ loopx doctor
978
+ ```
979
+ ````
980
+
981
+ - [ ] **Step 5: Update English README archive compatibility**
982
+
983
+ In `README.md`, after the paragraph that says `finish` is the terminal completion step, add:
984
+
985
+ ```markdown
986
+ ### Archive compatibility
987
+
988
+ `archive` is not part of the public v1 finish flow. Older runtime state may still contain archive fields or a hidden `loopx archive <slug>` compatibility command, but normal users should complete work through `finish` and the public finish audit commands above.
989
+ ```
990
+
991
+ - [ ] **Step 6: Update Chinese README quickstart and CLI behavior**
992
+
993
+ In `README.zh-CN.md`, after the recommended v1 flow block, add:
994
+
995
+ ````markdown
996
+ ## 快速开始
997
+
998
+ ```bash
999
+ loopx install-skills --target all --yes
1000
+ loopx init --slug my-feature
1001
+ loopx clarify my-feature
1002
+ loopx status my-feature
1003
+ ```
1004
+
1005
+ 默认输出面向人类,例如 `loopx init`、`loopx doctor` 和 `loopx install-skills`。当 agent 或脚本需要完整 runtime payload 时使用 `--json`:
1006
+
1007
+ ```bash
1008
+ loopx init --slug my-feature --json
1009
+ loopx doctor --json
1010
+ loopx install-skills --target all --json
1011
+ ```
1012
+ ````
1013
+
1014
+ - [ ] **Step 7: Update Chinese README install section**
1015
+
1016
+ In `README.zh-CN.md`, in the install section after the path list, add:
1017
+
1018
+ ````markdown
1019
+ 只检查、不写文件:
1020
+
1021
+ ```bash
1022
+ loopx install-skills --target all --dry-run
1023
+ ```
1024
+
1025
+ npm postinstall 阶段跳过自动安装:
1026
+
1027
+ ```bash
1028
+ LOOPX_SKIP_POSTINSTALL=1 npm install -g @ai-content-space/loopx
1029
+ LOOPX_POSTINSTALL=0 npm install -g @ai-content-space/loopx
1030
+ ```
1031
+
1032
+ 只在当前进程禁用 loopx hooks:
1033
+
1034
+ ```bash
1035
+ LOOPX_HOOKS=0 codex
1036
+ ```
1037
+
1038
+ 修复中断或冲突的安装:
1039
+
1040
+ ```bash
1041
+ loopx repair-install
1042
+ loopx doctor
1043
+ ```
1044
+ ````
1045
+
1046
+ - [ ] **Step 8: Update Chinese README archive compatibility**
1047
+
1048
+ In `README.zh-CN.md`, after the paragraph that says `finish` is the terminal completion step, add:
1049
+
1050
+ ```markdown
1051
+ ### Archive 兼容性
1052
+
1053
+ archive 不属于公开 v1 finish 流程。旧 runtime state 仍可能包含 archive 字段,也可能通过隐藏的 `loopx archive <slug>` 兼容命令处理历史状态,但普通用户应通过 `finish` 和上面的公开 finish audit 命令完成工作。
1054
+ ```
1055
+
1056
+ - [ ] **Step 9: Update CLI command lists**
1057
+
1058
+ In both README files:
1059
+
1060
+ - Change `loopx init ...` to include `[--json]`.
1061
+ - Change `loopx install-skills ...` to include `[--dry-run] [--json]`.
1062
+ - Change `loopx doctor` to `loopx doctor [--json]`.
1063
+ - Keep `loopx archive` absent from public CLI lists.
1064
+
1065
+ - [ ] **Step 10: Run docs tests**
1066
+
1067
+ Run:
1068
+
1069
+ ```bash
1070
+ node --test --test-name-pattern "keeps public docs structurally valid" test/skill-governance.test.mjs
1071
+ node scripts/verify-skills.mjs
1072
+ ```
1073
+
1074
+ Expected: both commands pass.
1075
+
1076
+ - [ ] **Step 11: Commit**
1077
+
1078
+ ```bash
1079
+ git add README.md README.zh-CN.md test/skill-governance.test.mjs scripts/verify-skills.mjs
1080
+ git commit -m "docs: document cli onboarding and install controls"
1081
+ ```
1082
+
1083
+ ---
1084
+
1085
+ ### Task 5: Tighten Published Skill Surface
1086
+
1087
+ **Files:**
1088
+ - Modify: `package.json`
1089
+ - Modify: `test/skill-governance.test.mjs`
1090
+ - Modify: `scripts/verify-skills.mjs`
1091
+
1092
+ - [ ] **Step 1: Write failing package surface test**
1093
+
1094
+ In `test/skill-governance.test.mjs`, replace the test named `keeps deprecated local skills and plugin tests out of the npm package` with:
1095
+
1096
+ ```js
1097
+ it('publishes only bundled root skills plus resolver', async () => {
1098
+ const { stdout } = await execFileAsync('npm', ['pack', '--dry-run', '--json'], { cwd: repoRoot });
1099
+ const [pack] = JSON.parse(stdout);
1100
+ const paths = pack.files.map((file) => file.path);
1101
+ const packagedSkillDirs = [...new Set(
1102
+ paths
1103
+ .filter((path) => path.startsWith('skills/') && path.endsWith('/SKILL.md'))
1104
+ .map((path) => path.split('/')[1]),
1105
+ )].sort();
1106
+
1107
+ assert.deepEqual(packagedSkillDirs, [...LOOPX_BUNDLED_SKILLS].sort());
1108
+ assert.equal(paths.includes('skills/RESOLVER.md'), true);
1109
+ assert.equal(paths.some((path) => path.startsWith('skills/deepsearch/')), false);
1110
+ assert.equal(paths.includes('plugins/loopx/scripts/plugin-install.test.mjs'), false);
1111
+ });
1112
+ ```
1113
+
1114
+ - [ ] **Step 2: Run package surface test and confirm failure**
1115
+
1116
+ Run:
1117
+
1118
+ ```bash
1119
+ node --test --test-name-pattern "publishes only bundled root skills plus resolver" test/skill-governance.test.mjs
1120
+ ```
1121
+
1122
+ Expected: fail because `package.json` currently includes broad `skills/`, which publishes non-bundled local skill sources such as `skills/deepsearch/`.
1123
+
1124
+ - [ ] **Step 3: Replace broad package skill inclusion**
1125
+
1126
+ In `package.json`, replace:
1127
+
1128
+ ```json
1129
+ "skills/",
1130
+ ```
1131
+
1132
+ with the explicit bundled surface:
1133
+
1134
+ ```json
1135
+ "skills/RESOLVER.md",
1136
+ "skills/clarify/",
1137
+ "skills/debug/",
1138
+ "skills/doc-readability/",
1139
+ "skills/exec/",
1140
+ "skills/final-review/",
1141
+ "skills/finish/",
1142
+ "skills/fix-review/",
1143
+ "skills/go-style/",
1144
+ "skills/kratos/",
1145
+ "skills/plan/",
1146
+ "skills/refactor-plan/",
1147
+ "skills/review/",
1148
+ "skills/spec/",
1149
+ "skills/subagent-exec/",
1150
+ "skills/tdd/",
1151
+ "skills/verify/",
1152
+ ```
1153
+
1154
+ Keep `plugins/loopx/` in the package because the plugin mirror remains part of the published surface.
1155
+
1156
+ - [ ] **Step 4: Add release verifier package surface assertion**
1157
+
1158
+ In `scripts/verify-skills.mjs`, after the existing `assert.equal(packageJson.files.includes('scripts/claude-workflow-hook.mjs'), true, ...)`, add:
1159
+
1160
+ ```js
1161
+ assert.equal(packageJson.files.includes('skills/'), false, 'npm package must not include broad skills/ surface');
1162
+ assert.equal(packageJson.files.includes('skills/RESOLVER.md'), true, 'npm package must include skills/RESOLVER.md');
1163
+ for (const skillName of LOOPX_BUNDLED_SKILLS) {
1164
+ assert.equal(packageJson.files.includes(`skills/${skillName}/`), true, `npm package missing bundled skill ${skillName}`);
1165
+ }
1166
+ ```
1167
+
1168
+ - [ ] **Step 5: Run package governance tests**
1169
+
1170
+ Run:
1171
+
1172
+ ```bash
1173
+ node --test --test-name-pattern "publishes only bundled root skills plus resolver|keeps a resolver" test/skill-governance.test.mjs
1174
+ node scripts/verify-skills.mjs
1175
+ npm pack --dry-run --json
1176
+ ```
1177
+
1178
+ Expected:
1179
+
1180
+ - Node tests pass.
1181
+ - `node scripts/verify-skills.mjs` prints `ok: verified 16 loopx bundled skills`.
1182
+ - `npm pack --dry-run --json` includes bundled skill directories and does not include `skills/deepsearch/`.
1183
+
1184
+ - [ ] **Step 6: Commit**
1185
+
1186
+ ```bash
1187
+ git add package.json test/skill-governance.test.mjs scripts/verify-skills.mjs
1188
+ git commit -m "chore: publish only bundled skills"
1189
+ ```
1190
+
1191
+ ---
1192
+
1193
+ ### Task 6: Final Verification
1194
+
1195
+ **Files:**
1196
+ - Verify only.
1197
+
1198
+ - [ ] **Step 1: Run full tests**
1199
+
1200
+ Run:
1201
+
1202
+ ```bash
1203
+ npm test
1204
+ ```
1205
+
1206
+ Expected: all repository tests pass.
1207
+
1208
+ - [ ] **Step 2: Run release governance**
1209
+
1210
+ Run:
1211
+
1212
+ ```bash
1213
+ node scripts/verify-skills.mjs
1214
+ ```
1215
+
1216
+ Expected:
1217
+
1218
+ ```text
1219
+ ok: verified 16 loopx bundled skills
1220
+ ```
1221
+
1222
+ - [ ] **Step 3: Smoke-test CLI human output**
1223
+
1224
+ Run:
1225
+
1226
+ ```bash
1227
+ node src/cli.mjs --help
1228
+ node src/cli.mjs doctor
1229
+ node src/cli.mjs doctor --json
1230
+ node src/cli.mjs install-skills --target codex --dry-run
1231
+ ```
1232
+
1233
+ Expected:
1234
+
1235
+ - Help starts with `Quick start:`.
1236
+ - Help does not include `loopx archive <slug>`.
1237
+ - `doctor` prints human summary.
1238
+ - `doctor --json` parses as JSON.
1239
+ - install dry-run says `writes: none`.
1240
+
1241
+ - [ ] **Step 4: Confirm package surface**
1242
+
1243
+ Run:
1244
+
1245
+ ```bash
1246
+ npm pack --dry-run --json
1247
+ ```
1248
+
1249
+ Expected: JSON output does not contain `skills/deepsearch/`.
1250
+
1251
+ - [ ] **Step 5: Commit any verification-only test fixture updates**
1252
+
1253
+ If verification requires no file changes, skip this step. If a test fixture or README assertion had to be corrected during verification, commit only that scoped correction:
1254
+
1255
+ ```bash
1256
+ git add <changed-files>
1257
+ git commit -m "fix: align cli onboarding verification"
1258
+ ```
1259
+
1260
+ ## Self-Review
1261
+
1262
+ - **Spec coverage:** The plan covers all three requested classes: first-use CLI output (`init`, `doctor`, help), archive product relationship, and install/package surface.
1263
+ - **Archive scope:** The plan removes archive from public guidance and recommendations while preserving hidden runtime compatibility. Full deletion of archival runtime code is intentionally left out because it would touch old spec-delta archival behavior, migration handling, and many existing tests.
1264
+ - **Placeholder scan:** No step asks an implementer to invent missing behavior; all output contracts, tests, file paths, and command expectations are explicit.
1265
+ - **Type consistency:** The plan consistently uses `--json`, `--dry-run`, `LOOPX_SKIP_POSTINSTALL`, `LOOPX_POSTINSTALL`, `LOOPX_HOOKS`, `$finish`, and `loopx approve <slug> --from review --to done`.
1266
+ - **Design drift:** The plan follows the approved direction from the conversation and does not introduce a new workflow stage or automatic git operation.
1267
+
1268
+ ## Execution Handoff
1269
+
1270
+ Plan complete and saved to `docs/loopx/plans/2026-06-09-cli-onboarding-install-surface.md`.
1271
+
1272
+ Two execution options:
1273
+
1274
+ 1. Subagent Exec (recommended) - dispatch a fresh subagent per task, review between tasks, fast iteration
1275
+ 2. Inline Execution - execute tasks in this session using exec, batch execution with checkpoints
1276
+
1277
+ Which approach?