@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.
- package/README.md +106 -10
- package/README.zh-CN.md +106 -10
- 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
- package/docs/loopx/memory/2026-06-09-stale-archive-hook-guidance.md +15 -0
- package/docs/loopx/memory/README.md +25 -0
- package/docs/loopx/plans/2026-06-08-finish-audit-change-window.md +933 -0
- package/docs/loopx/plans/2026-06-08-finish-learning-audit.md +410 -0
- package/docs/loopx/plans/2026-06-09-cli-onboarding-install-surface.md +1277 -0
- package/docs/loopx/specs/installation.md +33 -0
- package/package.json +18 -2
- package/plugins/loopx/.codex-plugin/plugin.json +1 -1
- package/plugins/loopx/skills/clarify/SKILL.md +1 -1
- package/plugins/loopx/skills/debug/SKILL.md +1 -1
- package/plugins/loopx/skills/doc-readability/SKILL.md +1 -1
- package/plugins/loopx/skills/exec/SKILL.md +11 -1
- package/plugins/loopx/skills/final-review/SKILL.md +1 -1
- package/plugins/loopx/skills/finish/SKILL.md +39 -7
- package/plugins/loopx/skills/fix-review/SKILL.md +1 -1
- package/plugins/loopx/skills/go-style/SKILL.md +1 -1
- package/plugins/loopx/skills/kratos/SKILL.md +1 -1
- package/plugins/loopx/skills/plan/SKILL.md +1 -1
- package/plugins/loopx/skills/refactor-plan/SKILL.md +1 -1
- package/plugins/loopx/skills/review/SKILL.md +1 -1
- package/plugins/loopx/skills/spec/SKILL.md +1 -1
- package/plugins/loopx/skills/subagent-exec/SKILL.md +13 -1
- package/plugins/loopx/skills/tdd/SKILL.md +1 -1
- package/plugins/loopx/skills/verify/SKILL.md +1 -1
- package/scripts/claude-workflow-hook.mjs +50 -1
- package/scripts/codex-workflow-hook.mjs +33 -12
- package/scripts/install-skills.mjs +58 -3
- package/scripts/verify-skills.mjs +83 -7
- package/skills/clarify/SKILL.md +1 -1
- package/skills/debug/SKILL.md +1 -1
- package/skills/doc-readability/SKILL.md +1 -1
- package/skills/exec/SKILL.md +11 -1
- package/skills/final-review/SKILL.md +1 -1
- package/skills/finish/SKILL.md +39 -7
- package/skills/fix-review/SKILL.md +1 -1
- package/skills/go-style/SKILL.md +1 -1
- package/skills/kratos/SKILL.md +1 -1
- package/skills/plan/SKILL.md +1 -1
- package/skills/refactor-plan/SKILL.md +1 -1
- package/skills/review/SKILL.md +1 -1
- package/skills/spec/SKILL.md +1 -1
- package/skills/subagent-exec/SKILL.md +13 -1
- package/skills/tdd/SKILL.md +1 -1
- package/skills/verify/SKILL.md +1 -1
- package/src/cli.mjs +473 -86
- package/src/finish-runtime.mjs +1184 -0
- package/src/install-discovery.mjs +37 -0
- package/src/next-skill.mjs +8 -10
- package/src/workflow.mjs +19 -26
- 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?
|