@ai-content-space/loopx 0.2.3 → 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 +222 -0
- package/plugins/loopx/skills/doc-readability/references/prd.md +269 -0
- 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/RESOLVER.md +3 -1
- package/skills/clarify/SKILL.md +1 -1
- package/skills/debug/SKILL.md +1 -1
- package/skills/doc-readability/SKILL.md +222 -0
- package/skills/doc-readability/references/prd.md +269 -0
- 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 +38 -0
- package/src/next-skill.mjs +8 -10
- package/src/workflow.mjs +19 -26
- 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 {
|
|
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
|
|
29
|
-
' loopx
|
|
30
|
-
' loopx
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
174
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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(`
|
|
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
|
-
|
|
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 =
|
|
506
|
+
const installOptions = shouldPromptInstallOptions(options)
|
|
220
507
|
? await promptInstallOptions()
|
|
221
508
|
: installOptionsFromArgs(options);
|
|
222
509
|
if (!installOptions) {
|
|
223
|
-
|
|
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
|
|
520
|
+
const env = {
|
|
227
521
|
...process.env,
|
|
228
522
|
LOOPX_INSTALL_CWD: process.cwd(),
|
|
229
|
-
}
|
|
230
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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': {
|