@ai-content-space/loopx 0.2.4 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +106 -10
  2. package/README.zh-CN.md +106 -10
  3. package/docs/loopx/design/finish/345/255/246/344/271/240/345/256/241/350/256/241/351/234/200/346/261/202/350/256/276/350/256/241/346/226/207/346/241/243.md +707 -0
  4. package/docs/loopx/memory/2026-06-09-stale-archive-hook-guidance.md +15 -0
  5. package/docs/loopx/memory/README.md +25 -0
  6. package/docs/loopx/plans/2026-06-08-finish-audit-change-window.md +933 -0
  7. package/docs/loopx/plans/2026-06-08-finish-learning-audit.md +410 -0
  8. package/docs/loopx/plans/2026-06-09-cli-onboarding-install-surface.md +1277 -0
  9. package/docs/loopx/specs/installation.md +33 -0
  10. package/package.json +18 -2
  11. package/plugins/loopx/.codex-plugin/plugin.json +1 -1
  12. package/plugins/loopx/skills/clarify/SKILL.md +1 -1
  13. package/plugins/loopx/skills/debug/SKILL.md +1 -1
  14. package/plugins/loopx/skills/doc-readability/SKILL.md +1 -1
  15. package/plugins/loopx/skills/exec/SKILL.md +11 -1
  16. package/plugins/loopx/skills/final-review/SKILL.md +1 -1
  17. package/plugins/loopx/skills/finish/SKILL.md +39 -7
  18. package/plugins/loopx/skills/fix-review/SKILL.md +1 -1
  19. package/plugins/loopx/skills/go-style/SKILL.md +1 -1
  20. package/plugins/loopx/skills/kratos/SKILL.md +1 -1
  21. package/plugins/loopx/skills/plan/SKILL.md +1 -1
  22. package/plugins/loopx/skills/refactor-plan/SKILL.md +1 -1
  23. package/plugins/loopx/skills/review/SKILL.md +1 -1
  24. package/plugins/loopx/skills/spec/SKILL.md +1 -1
  25. package/plugins/loopx/skills/subagent-exec/SKILL.md +13 -1
  26. package/plugins/loopx/skills/tdd/SKILL.md +1 -1
  27. package/plugins/loopx/skills/verify/SKILL.md +1 -1
  28. package/scripts/claude-workflow-hook.mjs +50 -1
  29. package/scripts/codex-workflow-hook.mjs +33 -12
  30. package/scripts/install-skills.mjs +58 -3
  31. package/scripts/verify-skills.mjs +83 -7
  32. package/skills/clarify/SKILL.md +1 -1
  33. package/skills/debug/SKILL.md +1 -1
  34. package/skills/doc-readability/SKILL.md +1 -1
  35. package/skills/exec/SKILL.md +11 -1
  36. package/skills/final-review/SKILL.md +1 -1
  37. package/skills/finish/SKILL.md +39 -7
  38. package/skills/fix-review/SKILL.md +1 -1
  39. package/skills/go-style/SKILL.md +1 -1
  40. package/skills/kratos/SKILL.md +1 -1
  41. package/skills/plan/SKILL.md +1 -1
  42. package/skills/refactor-plan/SKILL.md +1 -1
  43. package/skills/review/SKILL.md +1 -1
  44. package/skills/spec/SKILL.md +1 -1
  45. package/skills/subagent-exec/SKILL.md +13 -1
  46. package/skills/tdd/SKILL.md +1 -1
  47. package/skills/verify/SKILL.md +1 -1
  48. package/src/cli.mjs +473 -86
  49. package/src/finish-runtime.mjs +1184 -0
  50. package/src/install-discovery.mjs +37 -0
  51. package/src/next-skill.mjs +8 -10
  52. package/src/workflow.mjs +19 -26
  53. package/skills/deepsearch/SKILL.md +0 -38
package/src/cli.mjs CHANGED
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { readFileSync } from 'node:fs';
4
+ import { relative } from 'node:path';
4
5
  import { createInterface } from 'node:readline/promises';
5
6
 
6
7
  import { archiveStage, autopilotStage, approveStage, buildStage, clarifyStage, initWorkspace, planStage, reviewStage, statusSummary } from './workflow.mjs';
8
+ import { finishAuditStage, finishRecordStage, finishStartStage } from './finish-runtime.mjs';
7
9
  import { renderHtmlViews } from './html-views.mjs';
8
- import { installBundledSkills, installSkillsForTargets } from './install-discovery.mjs';
10
+ import { inspectInstallTargets, installSkillsForTargets, LOOPX_BUNDLED_SKILLS } from './install-discovery.mjs';
9
11
  import { nextCliCommand, nextSkillCommand, withNextSkill } from './next-skill.mjs';
10
12
  import { doctorRuntime, migrateLegacyRuntime } from './runtime-maintenance.mjs';
11
13
  import { setupWorkspaceContext } from './workspace-context.mjs';
@@ -14,24 +16,41 @@ const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.me
14
16
 
15
17
  function usage() {
16
18
  return [
19
+ 'Quick start:',
20
+ ' loopx install-skills --target all --yes',
21
+ ' loopx init --slug my-feature',
22
+ ' loopx clarify my-feature',
23
+ ' loopx status my-feature',
24
+ '',
17
25
  'Usage:',
18
26
  ' loopx --version',
19
- ' loopx init [--slug <slug>] [--enable-agent-delegation] [--auto-agent-delegation] [--agent-delegation-threshold <local|critic-only|parallel-review>]',
20
- ' loopx clarify <slug> [--standard|--deep]',
27
+ ' loopx init [--slug <slug>] [--enable-agent-delegation] [--auto-agent-delegation] [--agent-delegation-threshold <local|critic-only|parallel-review>] [--json]',
28
+ ' loopx clarify <slug> [--standard|--deep] [--json]',
29
+ ' loopx render [slug|--all]',
30
+ ' loopx status [slug] [--json]',
31
+ ' loopx next <slug> [--json]',
32
+ ' loopx setup-context',
33
+ ' loopx install-skills [--target <codex|claude|all>] [--project] [--mode <copy|symlink>] [--dir <path>] [--yes] [--dry-run] [--json]',
34
+ ' loopx doctor [--json]',
35
+ ' loopx migrate',
36
+ ' loopx repair-install',
37
+ '',
38
+ 'Advanced runtime commands: loopx help advanced',
39
+ ].join('\n');
40
+ }
41
+
42
+ function advancedUsage() {
43
+ return [
44
+ 'Advanced runtime commands:',
21
45
  ' loopx approve <slug> --from <stage> --to <stage>',
22
46
  ' loopx plan [slug] [--interactive] [--deliberate]',
23
47
  ' loopx build <slug> [--no-deslop]',
24
48
  ' loopx build --from-review <review-report-path> [--no-deslop]',
25
49
  ' loopx review <slug> [--reviewer <name>]',
26
- ' loopx archive <slug>',
27
50
  ' loopx autopilot <slug> [--reviewer <name>]',
28
- ' loopx render [slug|--all]',
29
- ' loopx status [slug] [--json]',
30
- ' loopx setup-context',
31
- ' loopx install-skills [--target <codex|claude|all>] [--project] [--mode <copy|symlink>] [--dir <path>] [--yes]',
32
- ' loopx doctor',
33
- ' loopx migrate',
34
- ' loopx repair-install',
51
+ ' loopx finish-start [slug] [--source <path>] [--json]',
52
+ ' loopx finish-audit [slug] [--baseline <git-ref>] [--json]',
53
+ ' loopx finish-record <audit-id-or-path> --action <merge|pr|keep|discard> --status <pending|done|failed|aborted> [--summary <text>] [--url <url>]',
35
54
  ].join('\n');
36
55
  }
37
56
 
@@ -67,6 +86,14 @@ function installOptionsFromArgs(options) {
67
86
  };
68
87
  }
69
88
 
89
+ function shouldPromptInstallOptions(options) {
90
+ return process.stdin.isTTY
91
+ && !options.get('--target')
92
+ && !options.get('--yes')
93
+ && !options.get('--json')
94
+ && !options.get('--dry-run');
95
+ }
96
+
70
97
  function parseArgs(argv) {
71
98
  const [command, ...rest] = argv;
72
99
  const positionals = [];
@@ -90,6 +117,173 @@ function parseArgs(argv) {
90
117
  return { command, positionals, options };
91
118
  }
92
119
 
120
+ function stringOption(options, name) {
121
+ const value = options.get(name);
122
+ if (value === undefined) {
123
+ return null;
124
+ }
125
+ if (value === true || String(value).trim() === '') {
126
+ throw new Error(`${name}_requires_value`);
127
+ }
128
+ return value;
129
+ }
130
+
131
+ function blockersForStatus(state) {
132
+ if (!state) {
133
+ return [];
134
+ }
135
+ const blockers = [];
136
+ const readinessKey = {
137
+ clarify: 'plan',
138
+ plan: 'build',
139
+ build: 'review',
140
+ review: 'done',
141
+ }[state.current_stage];
142
+ const readinessBlockers = readinessKey ? state.readiness?.[readinessKey]?.blockers : null;
143
+ if (Array.isArray(readinessBlockers)) {
144
+ blockers.push(...readinessBlockers);
145
+ }
146
+ for (const key of ['plan_blockers', 'build_blockers', 'autopilot_blockers']) {
147
+ if (Array.isArray(state[key])) {
148
+ blockers.push(...state[key]);
149
+ }
150
+ }
151
+ if (state.current_stage === 'review' && state.review_verdict === 'request-changes') {
152
+ blockers.push('review_request_changes');
153
+ }
154
+ if (state.stage_status === 'blocked' && blockers.length === 0) {
155
+ blockers.push('stage_status_blocked');
156
+ }
157
+ return [...new Set(blockers)];
158
+ }
159
+
160
+ const HUMAN_BLOCKER_MESSAGES = new Map([
161
+ ['unresolved_ambiguity', 'Resolve open clarification questions'],
162
+ ['clarify_current_round_required', 'Run clarify at least once'],
163
+ ['clarify_max_rounds_exceeded', 'Clarify round limit exceeded'],
164
+ ['clarify_non_goals_unresolved', 'Define non-goals'],
165
+ ['clarify_decision_boundaries_unresolved', 'Define decision boundaries'],
166
+ ['clarify_pressure_pass_incomplete', 'Complete clarify pressure pass'],
167
+ ['architect_review_incomplete', 'Complete planner architect review'],
168
+ ['acceptance_criteria_unresolved', 'Make acceptance criteria testable'],
169
+ ['verification_steps_unresolved', 'Define verification steps'],
170
+ ['execution_inputs_unresolved', 'Resolve execution inputs'],
171
+ ['missing_requirements_snapshot', 'Create requirements snapshot'],
172
+ ['missing_test_spec', 'Create test spec'],
173
+ ['missing_change_artifacts', 'Create change artifacts'],
174
+ ['missing_spec_delta_path', 'Create spec delta path'],
175
+ ['execution_record_missing', 'Create execution record'],
176
+ ['completion_audit_not_run', 'Run completion audit'],
177
+ ['workflow_not_done', 'Complete the workflow before archiving'],
178
+ ['review_request_changes', 'Address requested review changes'],
179
+ ['review_rework_required', 'Address review rework'],
180
+ ['plan_rework_required', 'Address plan rework'],
181
+ ['clarify_rework_required', 'Address clarify rework'],
182
+ ['stage_status_blocked', 'Resolve the current stage blocker'],
183
+ ]);
184
+
185
+ function humanizeStatusValue(value) {
186
+ return String(value || 'unknown').replaceAll('_', ' ');
187
+ }
188
+
189
+ function humanBlockerMessage(code) {
190
+ if (HUMAN_BLOCKER_MESSAGES.has(code)) {
191
+ return HUMAN_BLOCKER_MESSAGES.get(code);
192
+ }
193
+ const patterns = [
194
+ [/^critic_verdict_(.+)$/, (value) => `Planner critic verdict is ${humanizeStatusValue(value)}`],
195
+ [/^plan_package_(.+)$/, (value) => `Plan package is ${humanizeStatusValue(value)}`],
196
+ [/^change_artifacts_(.+)$/, (value) => `Change artifacts are ${humanizeStatusValue(value)}`],
197
+ [/^spec_delta_(.+)$/, (value) => `Spec delta is ${humanizeStatusValue(value)}`],
198
+ [/^execution_record_(.+)$/, (value) => `Execution record is ${humanizeStatusValue(value)}`],
199
+ [/^review_verdict_(.+)$/, (value) => `Review verdict is ${humanizeStatusValue(value)}`],
200
+ [/^review_status_(.+)$/, (value) => `Review status is ${humanizeStatusValue(value)}`],
201
+ [/^expansion_(.+)$/, (value) => `Expansion is ${humanizeStatusValue(value)}`],
202
+ [/^planning_(.+)$/, (value) => `Planning is ${humanizeStatusValue(value)}`],
203
+ [/^execution_(.+)$/, (value) => `Execution is ${humanizeStatusValue(value)}`],
204
+ [/^qa_(.+)$/, (value) => `QA is ${humanizeStatusValue(value)}`],
205
+ [/^validation_(.+)$/, (value) => `Validation is ${humanizeStatusValue(value)}`],
206
+ [/^review_(.+)$/, (value) => `Review is ${humanizeStatusValue(value)}`],
207
+ ];
208
+ for (const [pattern, format] of patterns) {
209
+ const match = pattern.exec(code);
210
+ if (match) {
211
+ return format(match[1]);
212
+ }
213
+ }
214
+ return humanizeStatusValue(code);
215
+ }
216
+
217
+ function humanBlockersForStatus(state) {
218
+ return [...new Set(blockersForStatus(state).map(humanBlockerMessage))];
219
+ }
220
+
221
+ function nextPayloadFromStatus(status, { human = false } = {}) {
222
+ const state = status.state || null;
223
+ let nextSkill = nextSkillCommand(state);
224
+ let nextCli = nextCliCommand(state);
225
+ if (human && nextCli) {
226
+ nextCli = null;
227
+ }
228
+ if (!nextSkill && !nextCli && state?.current_stage === 'clarify' && blockersForStatus(state).length > 0) {
229
+ nextSkill = `$clarify ${state.slug}`;
230
+ nextCli = human ? null : `loopx clarify ${state.slug}`;
231
+ }
232
+ return {
233
+ next_skill_command: nextSkill,
234
+ next_cli_command: nextCli,
235
+ next_action: status.next_action,
236
+ };
237
+ }
238
+
239
+ function printNext(status, { fallback = true } = {}) {
240
+ const payload = nextPayloadFromStatus(status, { human: true });
241
+ if (payload.next_skill_command) {
242
+ console.log(`next skill: ${payload.next_skill_command}`);
243
+ }
244
+ if (payload.next_cli_command) {
245
+ console.log(`next cli: ${payload.next_cli_command}`);
246
+ }
247
+ if (fallback && !payload.next_skill_command && !payload.next_cli_command) {
248
+ console.log(`next: ${payload.next_action}`);
249
+ }
250
+ const detailsSlug = status.slug ? ` ${status.slug}` : '';
251
+ console.log(`details: loopx status${detailsSlug} --json`);
252
+ }
253
+
254
+ function humanMissingArtifacts(status) {
255
+ if (status.state?.current_stage === 'clarify') {
256
+ return [];
257
+ }
258
+ return Array.isArray(status.missing_artifacts) ? status.missing_artifacts : [];
259
+ }
260
+
261
+ function humanMissingArtifactsText(status) {
262
+ const missing = humanMissingArtifacts(status);
263
+ if (missing.length > 0) {
264
+ return missing.join(', ');
265
+ }
266
+ if (status.state?.current_stage === 'clarify' && Array.isArray(status.missing_artifacts) && status.missing_artifacts.length > 0) {
267
+ return '(none for current stage)';
268
+ }
269
+ return '(none)';
270
+ }
271
+
272
+ function humanNextAction(status) {
273
+ const state = status.state || null;
274
+ if (state?.current_stage === 'clarify') {
275
+ if (nextSkillCommand(state)?.startsWith('$plan ')) {
276
+ return `Follow $plan ${state.slug}.`;
277
+ }
278
+ return 'Finish clarification, then follow $plan when ready.';
279
+ }
280
+ const payload = nextPayloadFromStatus(status, { human: true });
281
+ if (payload.next_skill_command) {
282
+ return `Follow ${payload.next_skill_command}.`;
283
+ }
284
+ return status.next_action;
285
+ }
286
+
93
287
  function printHumanStatus(status) {
94
288
  if (!status.initialized) {
95
289
  console.log('loopx workspace is not initialized.');
@@ -109,80 +303,165 @@ function printHumanStatus(status) {
109
303
 
110
304
  console.log(`workflow: ${status.slug}`);
111
305
  console.log(`contract: ${status.contract}`);
112
- console.log(`schema_version: ${status.schema_version}`);
113
306
  console.log(`stage: ${status.state?.current_stage ?? '(none)'}`);
114
- if (status.state?.current_stage === 'clarify') {
115
- console.log(`clarify_round: ${status.state.clarify_current_round}/${status.state.clarify_max_rounds}`);
116
- console.log(`clarify_ambiguity_score: ${status.state.clarify_ambiguity_score}`);
117
- console.log(`clarify_target_ambiguity_threshold: ${status.state.clarify_target_ambiguity_threshold}`);
118
- console.log(`clarify_gates: non_goals=${status.state.clarify_non_goals_resolved} decision_boundaries=${status.state.clarify_decision_boundaries_resolved} pressure_pass=${status.state.clarify_pressure_pass_complete}`);
119
- }
120
- if (status.state?.current_stage === 'plan') {
121
- console.log(`plan_iteration: ${status.state.plan_current_iteration}/${status.state.plan_max_iterations}`);
122
- console.log(`plan_consensus_mode: ${status.state.plan_consensus_mode}`);
123
- console.log(`plan_deliberate_mode: ${status.state.plan_deliberate_mode}`);
124
- console.log(`plan_architect_review_status: ${status.state.plan_architect_review_status}`);
125
- console.log(`plan_critic_verdict: ${status.state.plan_critic_verdict}`);
126
- console.log(`plan_artifact_status: ${status.state.plan_docs_status}`);
127
- console.log(`plan_delegation_mode: ${status.state.plan_delegation_mode ?? 'unknown'}`);
128
- console.log(`plan_delegation_recommended_mode: ${status.state.plan_delegation_recommended_mode ?? status.state.plan_delegation_mode ?? 'unknown'}`);
129
- console.log(`plan_delegation_actual_mode: ${status.state.plan_delegation_actual_mode ?? 'unknown'}`);
130
- console.log(`plan_delegation_authorization_status: ${status.state.plan_delegation_authorization_status ?? 'unknown'}`);
131
- console.log(`plan_delegation_decision_path: ${status.state.plan_delegation_decision_path ?? '(none)'}`);
132
- console.log(`source_requirements_status: ${status.state.source_requirements_status ?? 'unknown'}`);
133
- console.log(`requirement_traceability_path: ${status.state.requirement_traceability_path ?? '(none)'}`);
134
- console.log(`plan_blockers: ${Array.isArray(status.state.plan_blockers) && status.state.plan_blockers.length > 0 ? status.state.plan_blockers.join(', ') : '(none)'}`);
135
- }
136
- if (status.state?.current_stage === 'build') {
137
- console.log(`build_iteration: ${status.state.build_current_iteration}/${status.state.build_max_iterations}`);
138
- console.log(`build_parallel_mode: ${status.state.build_parallel_mode}`);
139
- console.log(`build_verification_status: ${status.state.build_verification_status}`);
140
- console.log(`build_architect_verification_status: ${status.state.build_architect_verification_status}`);
141
- console.log(`build_deslop_status: ${status.state.build_deslop_status}`);
142
- console.log(`build_regression_status: ${status.state.build_regression_status}`);
143
- console.log(`context_manifest_status: ${status.state.context_manifest_status ?? 'unknown'}`);
144
- console.log(`build_blockers: ${Array.isArray(status.state.build_blockers) && status.state.build_blockers.length > 0 ? status.state.build_blockers.join(', ') : '(none)'}`);
145
- }
146
- if (status.state?.workspace_journal_path) {
147
- console.log(`workspace_journal_path: ${status.state.workspace_journal_path}`);
148
- }
149
- if (status.state?.change_artifacts_status) {
150
- console.log(`change_artifacts_status: ${status.state.change_artifacts_status}`);
151
- console.log(`spec_delta_status: ${status.state.spec_delta_status ?? 'unknown'}`);
152
- console.log(`spec_sync_status: ${status.state.spec_sync_status ?? 'unknown'}`);
153
- console.log(`archive_status: ${status.state.archive_status ?? 'unknown'}`);
154
- }
155
- if (status.state?.readiness && status.state?.authorization) {
156
- for (const key of ['plan', 'build', 'review', 'done', 'archive']) {
157
- if (status.state.readiness[key]) {
158
- console.log(`readiness_${key}: ${status.state.readiness[key].ready}`);
159
- }
160
- if (status.state.authorization[key]) {
161
- console.log(`authorization_${key}: ${status.state.authorization[key].authorized}`);
162
- }
163
- }
164
- }
307
+ const blockers = blockersForStatus(status.state);
308
+ const humanBlockers = humanBlockersForStatus(status.state);
309
+ console.log(`blocked: ${blockers.length > 0 ? 'yes' : 'no'}`);
310
+ console.log(`blockers: ${humanBlockers.length > 0 ? humanBlockers.join(', ') : '(none)'}`);
165
311
  if (status.hook) {
166
312
  console.log(`hook_enabled: ${status.hook.enabled}`);
167
313
  }
168
- if (status.state?.autopilot_current_phase && status.state.autopilot_current_phase !== 'none') {
169
- console.log(`autopilot_current_phase: ${status.state.autopilot_current_phase}`);
170
- console.log(`autopilot_completed: ${status.state.autopilot_completed}`);
171
- console.log(`autopilot_blockers: ${Array.isArray(status.state.autopilot_blockers) && status.state.autopilot_blockers.length > 0 ? status.state.autopilot_blockers.join(', ') : '(none)'}`);
314
+ console.log(`missing artifacts: ${humanMissingArtifactsText(status)}`);
315
+ printNext(status, { fallback: false });
316
+ console.log(`next: ${humanNextAction(status)}`);
317
+ }
318
+
319
+ function displayPathFromCwd(path) {
320
+ if (!path) {
321
+ return '(none)';
322
+ }
323
+ const relativePath = relative(process.cwd(), path);
324
+ if (relativePath && !relativePath.startsWith('..')) {
325
+ return relativePath;
326
+ }
327
+ return path;
328
+ }
329
+
330
+ function printHumanClarify(result) {
331
+ const state = result.state || {};
332
+ const status = {
333
+ slug: state.slug,
334
+ state,
335
+ next_action: state.recommended_next_action || 'Run loopx status for the next step.',
336
+ };
337
+ const blockers = blockersForStatus(state);
338
+ const humanBlockers = humanBlockersForStatus(state);
339
+ const payload = nextPayloadFromStatus(status, { human: true });
340
+ const slug = state.slug || '(none)';
341
+ console.log(`workflow: ${slug}`);
342
+ console.log(`stage: ${state.current_stage || 'clarify'}`);
343
+ console.log(`profile: ${state.clarify_profile || 'standard'}`);
344
+ console.log(`blocked: ${blockers.length > 0 ? 'yes' : 'no'}`);
345
+ console.log(`blockers: ${humanBlockers.length > 0 ? humanBlockers.join(', ') : '(none)'}`);
346
+ console.log(`open questions: ${state.unresolved_ambiguity_count ?? 0}`);
347
+ const firstQuestion = Array.isArray(state.ambiguity_items)
348
+ ? state.ambiguity_items.find((item) => item?.status !== 'resolved' && item?.question)?.question
349
+ : null;
350
+ if (firstQuestion) {
351
+ console.log(`first question: ${firstQuestion}`);
352
+ }
353
+ console.log(`round: ${state.clarify_current_round ?? 0}/${state.clarify_max_rounds ?? '?'}`);
354
+ console.log(`intake: ${displayPathFromCwd(state.spec_artifact_path)}`);
355
+ if (payload.next_skill_command) {
356
+ console.log(`next skill: ${payload.next_skill_command}`);
357
+ }
358
+ if (payload.next_cli_command) {
359
+ console.log(`next cli: ${payload.next_cli_command}`);
172
360
  }
173
- console.log(`requested_transition: ${status.state?.requested_transition ?? 'none'}`);
174
- console.log(`last_confirmed_transition: ${status.state?.last_confirmed_transition ?? 'none'}`);
175
- console.log(`pending_user_decision: ${status.state?.pending_user_decision ?? 'none'}`);
176
- console.log(`missing artifacts: ${status.missing_artifacts.length > 0 ? status.missing_artifacts.join(', ') : '(none)'}`);
177
- const nextSkill = nextSkillCommand(status.state);
178
- if (nextSkill) {
179
- console.log(`next skill: ${nextSkill}`);
361
+ if (!payload.next_skill_command && !payload.next_cli_command) {
362
+ console.log(`next: ${payload.next_action}`);
180
363
  }
181
- const nextCli = nextCliCommand(status.state);
182
- if (nextCli) {
183
- console.log(`next cli: ${nextCli}`);
364
+ console.log(`details: loopx clarify ${slug} --json`);
365
+ console.log(`status: loopx status ${slug}`);
366
+ }
367
+
368
+ function printHumanInit(result, options = new Map()) {
369
+ const workflow = result.workflow?.state ?? null;
370
+ console.log('loopx workspace initialized');
371
+ console.log(`workspace: ${result.workspaceRoot}`);
372
+ if (!workflow) {
373
+ console.log('workflow: (none)');
374
+ console.log('next: loopx clarify <slug>');
375
+ console.log('details: loopx init --json');
376
+ return;
184
377
  }
185
- console.log(`next: ${status.next_action}`);
378
+ console.log(`workflow: ${workflow.slug}`);
379
+ console.log(`stage: ${workflow.current_stage ?? '(none)'}`);
380
+ console.log(`next: loopx clarify ${workflow.slug}`);
381
+ const slug = options.get('--slug') || workflow.slug;
382
+ console.log(`details: loopx init --slug ${slug} --json`);
383
+ }
384
+
385
+ function countInstallConflicts(result) {
386
+ return Object.values(result.installCheck?.results || {})
387
+ .reduce((sum, target) => sum + (Array.isArray(target.conflicts) ? target.conflicts.length : 0), 0);
388
+ }
389
+
390
+ function printHumanDoctor(result) {
391
+ const ok = !result.mixedRuntimeRoots && result.installCheck?.ok === true;
392
+ console.log(`loopx doctor: ${ok ? 'ok' : 'attention needed'}`);
393
+ console.log(`workspace: ${result.loopxRoot ?? result.workspaceRoot ?? '(unknown)'}`);
394
+ if (result.mixedRuntimeRoots) {
395
+ console.log('runtime roots: mixed .loopx and .LoopX detected');
396
+ } else {
397
+ console.log('runtime roots: ok');
398
+ }
399
+ console.log(`install: ${result.installCheck?.ok === true ? 'ok' : 'failed'}`);
400
+ const conflicts = countInstallConflicts(result);
401
+ if (conflicts > 0) {
402
+ console.log(`conflicts: ${conflicts}`);
403
+ }
404
+ if (result.hook) {
405
+ console.log(`hooks: ${result.hook.enabled ? 'enabled' : 'disabled'}`);
406
+ }
407
+ if (!ok) {
408
+ console.log('fix:');
409
+ console.log(' loopx repair-install');
410
+ console.log(' LOOPX_HOOKS=0 disables loopx hooks for the current process');
411
+ }
412
+ console.log('details: loopx doctor --json');
413
+ }
414
+
415
+ function installTargetNames(result) {
416
+ return Array.isArray(result.targets) && result.targets.length > 0 ? result.targets : Object.keys(result.results || {});
417
+ }
418
+
419
+ function countInstalledSkills(result) {
420
+ return Object.values(result.results || {})
421
+ .reduce((sum, target) => sum + (Array.isArray(target.installed) ? target.installed.length : 0), 0);
422
+ }
423
+
424
+ function countInstallSkipped(result) {
425
+ return Object.values(result.results || {})
426
+ .reduce((sum, target) => sum + (Array.isArray(target.skipped) ? target.skipped.length : 0), 0);
427
+ }
428
+
429
+ function installTargetArgument(result) {
430
+ const targets = installTargetNames(result);
431
+ return targets.length === 2 && targets.includes('codex') && targets.includes('claude') ? 'all' : targets[0];
432
+ }
433
+
434
+ function printHumanInstall(result, { dryRun = false } = {}) {
435
+ if (dryRun) {
436
+ console.log('loopx install-skills dry run');
437
+ for (const target of installTargetNames(result)) {
438
+ console.log(`target: ${target}`);
439
+ }
440
+ console.log(`skills: ${LOOPX_BUNDLED_SKILLS.length} bundled`);
441
+ console.log('writes: none');
442
+ console.log(`next: loopx install-skills --target ${installTargetArgument(result)} --yes`);
443
+ return;
444
+ }
445
+
446
+ console.log(`loopx install-skills: ${result.ok === false ? 'attention needed' : 'ok'}`);
447
+ console.log(`targets: ${installTargetNames(result).join(', ')}`);
448
+ console.log(`installed skills: ${countInstalledSkills(result)}`);
449
+ const conflicts = countInstallConflicts({ installCheck: result });
450
+ console.log(`conflicts: ${conflicts}`);
451
+ const skipped = countInstallSkipped(result);
452
+ if (skipped > 0) {
453
+ console.log(`skipped user-modified: ${skipped}`);
454
+ }
455
+ console.log('paths:');
456
+ for (const target of installTargetNames(result)) {
457
+ const inspection = result.results?.[target]?.inspection || result.results?.[target];
458
+ if (inspection?.installedSkillsRoot) {
459
+ console.log(` ${target} skills: ${inspection.installedSkillsRoot}`);
460
+ }
461
+ }
462
+ console.log('repair: loopx repair-install');
463
+ console.log('disable hooks for one process: LOOPX_HOOKS=0');
464
+ console.log('details: loopx install-skills --json');
186
465
  }
187
466
 
188
467
  async function main() {
@@ -191,6 +470,10 @@ async function main() {
191
470
  console.log(packageJson.version);
192
471
  return;
193
472
  }
473
+ if (command === 'help' && positionals[0] === 'advanced') {
474
+ console.log(advancedUsage());
475
+ return;
476
+ }
194
477
  if (!command || command === 'help' || command === '--help' || command === '-h') {
195
478
  console.log(usage());
196
479
  return;
@@ -207,7 +490,11 @@ async function main() {
207
490
  threshold: options.get('--agent-delegation-threshold'),
208
491
  },
209
492
  });
210
- console.log(JSON.stringify({ ok: true, command, workspaceRoot: result.workspaceRoot, workflow: result.workflow?.state ?? null }, null, 2));
493
+ if (options.get('--json')) {
494
+ console.log(JSON.stringify({ ok: true, command, workspaceRoot: result.workspaceRoot, workflow: result.workflow?.state ?? null }, null, 2));
495
+ } else {
496
+ printHumanInit(result, options);
497
+ }
211
498
  return;
212
499
  }
213
500
  case 'setup-context': {
@@ -216,24 +503,46 @@ async function main() {
216
503
  return;
217
504
  }
218
505
  case 'install-skills': {
219
- const installOptions = process.stdin.isTTY && !options.get('--target') && !options.get('--yes')
506
+ const installOptions = shouldPromptInstallOptions(options)
220
507
  ? await promptInstallOptions()
221
508
  : installOptionsFromArgs(options);
222
509
  if (!installOptions) {
223
- console.log(JSON.stringify({ ok: false, command, cancelled: true }, null, 2));
510
+ const payload = { ok: false, command, cancelled: true };
511
+ if (options.get('--json')) {
512
+ console.log(JSON.stringify(payload, null, 2));
513
+ } else {
514
+ console.log('loopx install-skills cancelled');
515
+ console.log('next: loopx install-skills --target all --yes');
516
+ console.log('details: loopx install-skills --json');
517
+ }
224
518
  return;
225
519
  }
226
- const result = await installSkillsForTargets({
520
+ const env = {
227
521
  ...process.env,
228
522
  LOOPX_INSTALL_CWD: process.cwd(),
229
- }, installOptions);
230
- console.log(JSON.stringify({ ok: result.ok, command, ...result }, null, 2));
523
+ };
524
+ const result = options.get('--dry-run')
525
+ ? await inspectInstallTargets(env, installOptions)
526
+ : await installSkillsForTargets(env, installOptions);
527
+ const payload = { ok: result.ok, command, ...result };
528
+ if (options.get('--json')) {
529
+ console.log(JSON.stringify(payload, null, 2));
530
+ } else {
531
+ printHumanInstall(payload, { dryRun: Boolean(options.get('--dry-run')) });
532
+ }
533
+ if (payload.ok === false) {
534
+ process.exitCode = 1;
535
+ }
231
536
  return;
232
537
  }
233
538
  case 'clarify': {
234
539
  const profile = options.get('--deep') ? 'deep' : 'standard';
235
540
  const result = await clarifyStage(process.cwd(), positionals[0], { profile });
236
- console.log(JSON.stringify(withNextSkill({ ok: true, command, root: result.root, state: result.state }, result.state), null, 2));
541
+ if (options.get('--json')) {
542
+ console.log(JSON.stringify(withNextSkill({ ok: true, command, root: result.root, state: result.state }, result.state), null, 2));
543
+ } else {
544
+ printHumanClarify(result);
545
+ }
237
546
  return;
238
547
  }
239
548
  case 'approve': {
@@ -279,6 +588,69 @@ async function main() {
279
588
  console.log(JSON.stringify({ ok: true, command, root: result.root, state: result.state, runPath: result.runPath }, null, 2));
280
589
  return;
281
590
  }
591
+ case 'finish-start': {
592
+ const result = await finishStartStage(process.cwd(), positionals[0], {
593
+ source: stringOption(options, '--source'),
594
+ });
595
+ if (options.get('--json')) {
596
+ console.log(JSON.stringify({
597
+ ok: true,
598
+ command,
599
+ path: result.path,
600
+ latestPath: result.latestPath,
601
+ state: result.state,
602
+ }, null, 2));
603
+ } else {
604
+ console.log(`finish baseline: ${result.state.slug}`);
605
+ console.log(`path: ${result.path}`);
606
+ console.log(`head: ${result.state.head_short}`);
607
+ console.log(`source: ${result.state.source ?? '(none)'}`);
608
+ }
609
+ return;
610
+ }
611
+ case 'finish-audit': {
612
+ const result = await finishAuditStage(process.cwd(), positionals[0], {
613
+ baselineRef: stringOption(options, '--baseline'),
614
+ });
615
+ if (options.get('--json')) {
616
+ console.log(JSON.stringify({
617
+ ok: true,
618
+ command,
619
+ audit_id: result.auditId,
620
+ auditId: result.auditId,
621
+ root: result.root,
622
+ state: result.state,
623
+ reportPath: result.reportPath,
624
+ statePath: result.statePath,
625
+ }, null, 2));
626
+ } else {
627
+ console.log(`finish audit: ${result.auditId}`);
628
+ console.log(`root: ${result.root}`);
629
+ console.log(`report: ${result.reportPath}`);
630
+ console.log(`state: ${result.statePath}`);
631
+ console.log(`slug: ${result.state?.slug ?? positionals[0] ?? '(unknown)'}`);
632
+ console.log(`status: ${result.state?.status ?? '(unknown)'}`);
633
+ }
634
+ return;
635
+ }
636
+ case 'finish-record': {
637
+ const result = await finishRecordStage(process.cwd(), positionals[0], {
638
+ action: options.get('--action'),
639
+ status: options.get('--status'),
640
+ summary: options.get('--summary') || null,
641
+ url: options.get('--url') || null,
642
+ });
643
+ console.log(JSON.stringify({
644
+ ok: true,
645
+ command,
646
+ root: result.root,
647
+ state: result.state,
648
+ choice: result.state.choice,
649
+ reportPath: result.reportPath,
650
+ statePath: result.statePath,
651
+ }, null, 2));
652
+ return;
653
+ }
282
654
  case 'render': {
283
655
  const result = await renderHtmlViews(process.cwd(), {
284
656
  slug: positionals[0],
@@ -296,9 +668,24 @@ async function main() {
296
668
  }
297
669
  return;
298
670
  }
671
+ case 'next': {
672
+ const result = await statusSummary(process.cwd(), positionals[0]);
673
+ const payload = { ok: true, command, slug: result.slug ?? null, ...nextPayloadFromStatus(result) };
674
+ if (options.get('--json')) {
675
+ console.log(JSON.stringify(payload, null, 2));
676
+ } else {
677
+ printNext(result);
678
+ }
679
+ return;
680
+ }
299
681
  case 'doctor': {
300
682
  const result = await doctorRuntime(process.cwd(), process.env);
301
- console.log(JSON.stringify({ ok: !result.mixedRuntimeRoots && result.installCheck.ok, command, ...result }, null, 2));
683
+ const payload = { ok: !result.mixedRuntimeRoots && result.installCheck.ok, command, ...result };
684
+ if (options.get('--json')) {
685
+ console.log(JSON.stringify(payload, null, 2));
686
+ } else {
687
+ printHumanDoctor(payload);
688
+ }
302
689
  return;
303
690
  }
304
691
  case 'migrate': {