agentxchain 2.103.0 → 2.105.0
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 +13 -7
- package/bin/agentxchain.js +16 -8
- package/dashboard/app.js +111 -7
- package/dashboard/components/blocked.js +95 -11
- package/dashboard/components/blockers.js +85 -86
- package/dashboard/components/coordinator-timeouts.js +13 -0
- package/dashboard/components/cross-repo.js +17 -12
- package/dashboard/components/gate.js +31 -11
- package/dashboard/components/initiative.js +173 -78
- package/dashboard/components/ledger.js +28 -0
- package/dashboard/components/live-status.js +39 -0
- package/dashboard/components/run-history.js +76 -1
- package/dashboard/components/timeline.js +5 -1
- package/dashboard/index.html +21 -0
- package/dashboard/live-observer.js +91 -0
- package/package.json +1 -1
- package/scripts/release-bump.sh +26 -3
- package/scripts/release-preflight.sh +82 -38
- package/src/commands/accept-turn.js +3 -3
- package/src/commands/decisions.js +98 -29
- package/src/commands/diff.js +27 -4
- package/src/commands/doctor.js +48 -16
- package/src/commands/generate.js +126 -1
- package/src/commands/history.js +21 -3
- package/src/commands/init.js +15 -97
- package/src/commands/multi.js +223 -54
- package/src/commands/phase.js +11 -13
- package/src/commands/reject-turn.js +1 -1
- package/src/commands/restart.js +28 -11
- package/src/commands/resume.js +6 -6
- package/src/commands/role.js +51 -14
- package/src/commands/run.js +5 -11
- package/src/commands/status.js +145 -13
- package/src/commands/step.js +36 -29
- package/src/lib/admission-control.js +14 -12
- package/src/lib/blocked-state.js +150 -0
- package/src/lib/conflict-actions.js +17 -0
- package/src/lib/context-section-parser.js +2 -0
- package/src/lib/continuity-status.js +1 -1
- package/src/lib/coordinator-blocker-presentation.js +127 -0
- package/src/lib/coordinator-event-narrative.js +43 -0
- package/src/lib/coordinator-gate-approval.js +98 -0
- package/src/lib/coordinator-gate-evaluation-presentation.js +57 -0
- package/src/lib/coordinator-next-actions.js +128 -0
- package/src/lib/coordinator-pending-gate-presentation.js +79 -0
- package/src/lib/coordinator-presentation-detail.js +11 -0
- package/src/lib/coordinator-repo-snapshots.js +53 -0
- package/src/lib/coordinator-repo-status-presentation.js +134 -0
- package/src/lib/dashboard/actions.js +105 -29
- package/src/lib/dashboard/bridge-server.js +7 -0
- package/src/lib/dashboard/coordinator-blockers.js +17 -0
- package/src/lib/dashboard/coordinator-repo-status.js +50 -0
- package/src/lib/dashboard/coordinator-timeout-status.js +34 -11
- package/src/lib/dashboard/state-reader.js +36 -1
- package/src/lib/dispatch-bundle.js +23 -0
- package/src/lib/export-diff.js +70 -38
- package/src/lib/export-verifier.js +3 -0
- package/src/lib/history-diff-summary.js +249 -0
- package/src/lib/manual-qa-fallback.js +18 -0
- package/src/lib/normalized-config.js +27 -22
- package/src/lib/planning-artifacts.js +131 -0
- package/src/lib/recent-event-summary.js +132 -0
- package/src/lib/repo-decisions.js +69 -28
- package/src/lib/report.js +353 -145
- package/src/lib/run-diff.js +4 -0
- package/src/lib/runtime-capabilities.js +222 -0
package/src/commands/doctor.js
CHANGED
|
@@ -11,6 +11,11 @@ import { loadNormalizedConfig, detectConfigVersion } from '../lib/normalized-con
|
|
|
11
11
|
import { readDaemonState, evaluateDaemonStatus } from '../lib/run-schedule.js';
|
|
12
12
|
import { getGovernedVersionSurface, formatGovernedVersionLabel } from '../lib/protocol-version.js';
|
|
13
13
|
import { PLUGIN_MANIFEST_FILE } from '../lib/plugins.js';
|
|
14
|
+
import {
|
|
15
|
+
getRoleRuntimeCapabilityContract,
|
|
16
|
+
summarizeRoleRuntimeCapability,
|
|
17
|
+
summarizeRuntimeCapabilityContract,
|
|
18
|
+
} from '../lib/runtime-capabilities.js';
|
|
14
19
|
|
|
15
20
|
export async function doctorCommand(opts = {}) {
|
|
16
21
|
const root = findProjectRoot(process.cwd());
|
|
@@ -76,8 +81,9 @@ function governedDoctor(root, rawConfig, opts) {
|
|
|
76
81
|
// 3. Runtime reachable — one sub-check per runtime
|
|
77
82
|
// Use normalized runtimes if available, otherwise fall back to raw config
|
|
78
83
|
const runtimes = (normalized && normalized.runtimes) || rawConfig.runtimes || {};
|
|
84
|
+
const rolesByRuntime = buildRolesByRuntime(normalized?.roles || {});
|
|
79
85
|
for (const [rtId, rt] of Object.entries(runtimes)) {
|
|
80
|
-
const check = checkRuntimeReachable(rtId, rt);
|
|
86
|
+
const check = checkRuntimeReachable(rtId, rt, rolesByRuntime[rtId] || []);
|
|
81
87
|
checks.push(check);
|
|
82
88
|
}
|
|
83
89
|
const connectorProbe = getConnectorProbeRecommendation(runtimes);
|
|
@@ -290,6 +296,12 @@ function governedDoctor(root, rawConfig, opts) {
|
|
|
290
296
|
? chalk.dim('INFO')
|
|
291
297
|
: chalk.red('FAIL');
|
|
292
298
|
console.log(` ${badge} ${c.name.padEnd(24)} ${chalk.dim(c.detail)}`);
|
|
299
|
+
if (c.runtime_contract) {
|
|
300
|
+
console.log(` ${chalk.dim(summarizeRuntimeCapabilityContract(c.runtime_contract))}`);
|
|
301
|
+
if (Array.isArray(c.bound_roles) && c.bound_roles.length > 0) {
|
|
302
|
+
console.log(` ${chalk.dim(`roles: ${c.bound_roles.map(summarizeRoleRuntimeCapability).join(' | ')}`)}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
293
305
|
}
|
|
294
306
|
|
|
295
307
|
console.log('');
|
|
@@ -309,25 +321,45 @@ function governedDoctor(root, rawConfig, opts) {
|
|
|
309
321
|
process.exit(failCount > 0 ? 1 : 0);
|
|
310
322
|
}
|
|
311
323
|
|
|
312
|
-
function
|
|
324
|
+
function buildRolesByRuntime(roles) {
|
|
325
|
+
const grouped = {};
|
|
326
|
+
for (const [roleId, role] of Object.entries(roles || {})) {
|
|
327
|
+
if (!role?.runtime_id) continue;
|
|
328
|
+
if (!grouped[role.runtime_id]) grouped[role.runtime_id] = [];
|
|
329
|
+
grouped[role.runtime_id].push([roleId, role]);
|
|
330
|
+
}
|
|
331
|
+
return grouped;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function attachRuntimeContract(baseCheck, rtId, rt, boundRoleEntries) {
|
|
335
|
+
const bound_roles = boundRoleEntries.map(([roleId, role]) => getRoleRuntimeCapabilityContract(roleId, role, rt));
|
|
336
|
+
return {
|
|
337
|
+
...baseCheck,
|
|
338
|
+
runtime_type: rt?.type || 'unknown',
|
|
339
|
+
runtime_contract: bound_roles[0]?.runtime_contract || getRoleRuntimeCapabilityContract('__unbound__', { write_authority: 'unknown' }, rt).runtime_contract,
|
|
340
|
+
bound_roles,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function checkRuntimeReachable(rtId, rt, boundRoleEntries = []) {
|
|
313
345
|
const base = { id: `runtime_${rtId}`, name: `Runtime: ${rtId}` };
|
|
314
346
|
|
|
315
347
|
if (!rt || !rt.type) {
|
|
316
|
-
return { ...base, level: 'warn', detail: 'No runtime type specified' };
|
|
348
|
+
return attachRuntimeContract({ ...base, level: 'warn', detail: 'No runtime type specified' }, rtId, rt, boundRoleEntries);
|
|
317
349
|
}
|
|
318
350
|
|
|
319
351
|
switch (rt.type) {
|
|
320
352
|
case 'manual':
|
|
321
|
-
return { ...base, level: 'pass', detail: 'Manual runtime (no binary needed)' };
|
|
353
|
+
return attachRuntimeContract({ ...base, level: 'pass', detail: 'Manual runtime (no binary needed)' }, rtId, rt, boundRoleEntries);
|
|
322
354
|
|
|
323
355
|
case 'local_cli': {
|
|
324
356
|
const cmd = Array.isArray(rt.command) ? rt.command[0] : (typeof rt.command === 'string' ? rt.command.split(/\s+/)[0] : null);
|
|
325
|
-
if (!cmd) return { ...base, level: 'warn', detail: 'No command configured' };
|
|
357
|
+
if (!cmd) return attachRuntimeContract({ ...base, level: 'warn', detail: 'No command configured' }, rtId, rt, boundRoleEntries);
|
|
326
358
|
try {
|
|
327
359
|
execSync(`command -v ${cmd}`, { stdio: 'ignore' });
|
|
328
|
-
return { ...base, level: 'pass', detail: `${cmd} binary found` };
|
|
360
|
+
return attachRuntimeContract({ ...base, level: 'pass', detail: `${cmd} binary found` }, rtId, rt, boundRoleEntries);
|
|
329
361
|
} catch {
|
|
330
|
-
return { ...base, level: 'fail', detail: `${cmd} not found in PATH` };
|
|
362
|
+
return attachRuntimeContract({ ...base, level: 'fail', detail: `${cmd} not found in PATH` }, rtId, rt, boundRoleEntries);
|
|
331
363
|
}
|
|
332
364
|
}
|
|
333
365
|
|
|
@@ -335,34 +367,34 @@ function checkRuntimeReachable(rtId, rt) {
|
|
|
335
367
|
const envVar = rt.auth_env;
|
|
336
368
|
if (!envVar) {
|
|
337
369
|
// ollama and similar providers may not require auth
|
|
338
|
-
return { ...base, level: 'pass', detail: `${rt.provider || 'unknown'} provider (no auth required)` };
|
|
370
|
+
return attachRuntimeContract({ ...base, level: 'pass', detail: `${rt.provider || 'unknown'} provider (no auth required)` }, rtId, rt, boundRoleEntries);
|
|
339
371
|
}
|
|
340
372
|
if (process.env[envVar]) {
|
|
341
|
-
return { ...base, level: 'pass', detail: `${envVar} is set` };
|
|
373
|
+
return attachRuntimeContract({ ...base, level: 'pass', detail: `${envVar} is set` }, rtId, rt, boundRoleEntries);
|
|
342
374
|
}
|
|
343
|
-
return { ...base, level: 'fail', detail: `${envVar} not set` };
|
|
375
|
+
return attachRuntimeContract({ ...base, level: 'fail', detail: `${envVar} not set` }, rtId, rt, boundRoleEntries);
|
|
344
376
|
}
|
|
345
377
|
|
|
346
378
|
case 'mcp': {
|
|
347
379
|
const transport = rt.transport || 'stdio';
|
|
348
380
|
if (transport === 'streamable_http') {
|
|
349
|
-
return { ...base, level: 'warn', detail: 'Remote MCP endpoint (cannot verify at doctor time)' };
|
|
381
|
+
return attachRuntimeContract({ ...base, level: 'warn', detail: 'Remote MCP endpoint (cannot verify at doctor time)' }, rtId, rt, boundRoleEntries);
|
|
350
382
|
}
|
|
351
383
|
const cmd = Array.isArray(rt.command) ? rt.command[0] : (typeof rt.command === 'string' ? rt.command.split(/\s+/)[0] : null);
|
|
352
|
-
if (!cmd) return { ...base, level: 'warn', detail: 'No MCP command configured' };
|
|
384
|
+
if (!cmd) return attachRuntimeContract({ ...base, level: 'warn', detail: 'No MCP command configured' }, rtId, rt, boundRoleEntries);
|
|
353
385
|
try {
|
|
354
386
|
execSync(`command -v ${cmd}`, { stdio: 'ignore' });
|
|
355
|
-
return { ...base, level: 'pass', detail: `${cmd} binary found` };
|
|
387
|
+
return attachRuntimeContract({ ...base, level: 'pass', detail: `${cmd} binary found` }, rtId, rt, boundRoleEntries);
|
|
356
388
|
} catch {
|
|
357
|
-
return { ...base, level: 'fail', detail: `${cmd} not found in PATH` };
|
|
389
|
+
return attachRuntimeContract({ ...base, level: 'fail', detail: `${cmd} not found in PATH` }, rtId, rt, boundRoleEntries);
|
|
358
390
|
}
|
|
359
391
|
}
|
|
360
392
|
|
|
361
393
|
case 'remote_agent':
|
|
362
|
-
return { ...base, level: 'warn', detail: 'Remote agent endpoint (cannot verify at doctor time)' };
|
|
394
|
+
return attachRuntimeContract({ ...base, level: 'warn', detail: 'Remote agent endpoint (cannot verify at doctor time)' }, rtId, rt, boundRoleEntries);
|
|
363
395
|
|
|
364
396
|
default:
|
|
365
|
-
return { ...base, level: 'warn', detail: `Unknown runtime type: ${rt.type}` };
|
|
397
|
+
return attachRuntimeContract({ ...base, level: 'warn', detail: `Unknown runtime type: ${rt.type}` }, rtId, rt, boundRoleEntries);
|
|
366
398
|
}
|
|
367
399
|
}
|
|
368
400
|
|
package/src/commands/generate.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
1
3
|
import chalk from 'chalk';
|
|
2
|
-
import { loadConfig } from '../lib/config.js';
|
|
4
|
+
import { loadConfig, loadProjectContext } from '../lib/config.js';
|
|
3
5
|
import { generateVSCodeFiles } from '../lib/generate-vscode.js';
|
|
6
|
+
import { loadGovernedTemplate } from '../lib/governed-templates.js';
|
|
7
|
+
import { buildGovernedPlanningArtifacts } from '../lib/planning-artifacts.js';
|
|
4
8
|
|
|
5
9
|
export async function generateCommand() {
|
|
6
10
|
const result = loadConfig();
|
|
@@ -42,3 +46,124 @@ export async function generateCommand() {
|
|
|
42
46
|
console.log(chalk.dim(' Select an agent from the Chat dropdown to start a turn.'));
|
|
43
47
|
console.log('');
|
|
44
48
|
}
|
|
49
|
+
|
|
50
|
+
function failPlanningGenerate(message, opts = {}) {
|
|
51
|
+
if (opts.json) {
|
|
52
|
+
console.log(JSON.stringify({ ok: false, error: message }, null, 2));
|
|
53
|
+
} else {
|
|
54
|
+
console.log(chalk.red(` ${message}`));
|
|
55
|
+
}
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function generatePlanningCommand(opts = {}) {
|
|
60
|
+
const context = loadProjectContext();
|
|
61
|
+
if (!context) {
|
|
62
|
+
failPlanningGenerate('No valid agentxchain.json found. Run `agentxchain init --governed` first.', opts);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (context.version !== 4) {
|
|
66
|
+
failPlanningGenerate('`generate planning` only works in governed repos.', opts);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const templateId = context.rawConfig.template || 'generic';
|
|
70
|
+
let template;
|
|
71
|
+
try {
|
|
72
|
+
template = loadGovernedTemplate(templateId);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
failPlanningGenerate(err.message, opts);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const projectName = context.config?.project?.name || context.rawConfig?.project?.name || 'AgentXchain Project';
|
|
78
|
+
const artifacts = buildGovernedPlanningArtifacts({
|
|
79
|
+
projectName,
|
|
80
|
+
routing: context.config.routing || {},
|
|
81
|
+
roles: context.config.roles || {},
|
|
82
|
+
template,
|
|
83
|
+
workflowKitConfig: context.config.workflow_kit || null,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const created = [];
|
|
87
|
+
const overwritten = [];
|
|
88
|
+
const skippedExisting = [];
|
|
89
|
+
|
|
90
|
+
for (const artifact of artifacts) {
|
|
91
|
+
const absPath = join(context.root, artifact.path);
|
|
92
|
+
if (existsSync(absPath)) {
|
|
93
|
+
if (opts.force) {
|
|
94
|
+
overwritten.push(artifact.path);
|
|
95
|
+
} else {
|
|
96
|
+
skippedExisting.push(artifact.path);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
created.push(artifact.path);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (opts.dryRun) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const parentDir = dirname(absPath);
|
|
108
|
+
if (!existsSync(parentDir)) {
|
|
109
|
+
mkdirSync(parentDir, { recursive: true });
|
|
110
|
+
}
|
|
111
|
+
writeFileSync(absPath, artifact.content);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const payload = {
|
|
115
|
+
ok: true,
|
|
116
|
+
mode: 'planning',
|
|
117
|
+
dry_run: Boolean(opts.dryRun),
|
|
118
|
+
force: Boolean(opts.force),
|
|
119
|
+
template: template.id,
|
|
120
|
+
project: projectName,
|
|
121
|
+
total_artifacts: artifacts.length,
|
|
122
|
+
created,
|
|
123
|
+
overwritten,
|
|
124
|
+
skipped_existing: skippedExisting,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (opts.json) {
|
|
128
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log('');
|
|
133
|
+
console.log(chalk.bold(' Generating governed planning artifacts...'));
|
|
134
|
+
console.log(chalk.dim(` Project: ${projectName}`));
|
|
135
|
+
console.log(chalk.dim(` Template: ${template.id}`));
|
|
136
|
+
console.log('');
|
|
137
|
+
|
|
138
|
+
if (created.length > 0) {
|
|
139
|
+
console.log(chalk.green(` ${opts.dryRun ? 'Would create' : 'Created'} ${created.length} artifact${created.length === 1 ? '' : 's'}:`));
|
|
140
|
+
for (const path of created) {
|
|
141
|
+
console.log(chalk.green(` ${path}`));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (overwritten.length > 0) {
|
|
146
|
+
console.log(chalk.yellow(` ${opts.dryRun ? 'Would overwrite' : 'Overwrote'} ${overwritten.length} artifact${overwritten.length === 1 ? '' : 's'}:`));
|
|
147
|
+
for (const path of overwritten) {
|
|
148
|
+
console.log(chalk.yellow(` ${path}`));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (skippedExisting.length > 0) {
|
|
153
|
+
console.log(chalk.dim(` Preserved ${skippedExisting.length} existing artifact${skippedExisting.length === 1 ? '' : 's'}:`));
|
|
154
|
+
for (const path of skippedExisting) {
|
|
155
|
+
console.log(chalk.dim(` ${path}`));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (created.length === 0 && overwritten.length === 0) {
|
|
160
|
+
console.log(chalk.dim(` ${opts.force ? 'Nothing to overwrite.' : 'All scaffold-owned planning artifacts already exist.'}`));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (opts.dryRun) {
|
|
164
|
+
console.log('');
|
|
165
|
+
console.log(chalk.dim(' No files were written. Re-run without `--dry-run` to apply.'));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log('');
|
|
169
|
+
}
|
package/src/commands/history.js
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { resolve } from 'path';
|
|
8
|
-
import { existsSync
|
|
8
|
+
import { existsSync } from 'fs';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
10
|
import { queryRunHistory, queryRunLineage, isInheritable } from '../lib/run-history.js';
|
|
11
|
-
import {
|
|
11
|
+
import { buildRunOutcomeSummary } from '../lib/history-diff-summary.js';
|
|
12
|
+
import { getRunTriggerLabel } from '../lib/run-provenance.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* @param {object} opts - { json?: boolean, limit?: number, status?: string, dir?: string }
|
|
@@ -67,7 +68,11 @@ export async function historyCommand(opts) {
|
|
|
67
68
|
});
|
|
68
69
|
|
|
69
70
|
if (opts.json) {
|
|
70
|
-
const enriched = entries.map(e => ({
|
|
71
|
+
const enriched = entries.map((e) => ({
|
|
72
|
+
...e,
|
|
73
|
+
inheritable: isInheritable(e),
|
|
74
|
+
outcome_summary: buildRunOutcomeSummary(e),
|
|
75
|
+
}));
|
|
71
76
|
console.log(JSON.stringify(enriched, null, 2));
|
|
72
77
|
return;
|
|
73
78
|
}
|
|
@@ -85,6 +90,7 @@ export async function historyCommand(opts) {
|
|
|
85
90
|
pad('#', 4),
|
|
86
91
|
pad('Run ID', 14),
|
|
87
92
|
pad('Status', 11),
|
|
93
|
+
pad('Outcome', 11),
|
|
88
94
|
pad('Trigger', 14),
|
|
89
95
|
pad('Ctx', 4),
|
|
90
96
|
pad('Phases', 8),
|
|
@@ -102,6 +108,7 @@ export async function historyCommand(opts) {
|
|
|
102
108
|
const idx = String(i + 1);
|
|
103
109
|
const runId = (entry.run_id || '—').slice(0, 12);
|
|
104
110
|
const status = formatStatus(entry.status);
|
|
111
|
+
const outcome = buildRunOutcomeSummary(entry);
|
|
105
112
|
const trigger = getRunTriggerLabel(entry.provenance);
|
|
106
113
|
const ctx = isInheritable(entry) ? '✓' : '—';
|
|
107
114
|
const phases = String(entry.phases_completed?.length || 0);
|
|
@@ -121,6 +128,7 @@ export async function historyCommand(opts) {
|
|
|
121
128
|
pad(idx, 4),
|
|
122
129
|
pad(runId, 14),
|
|
123
130
|
pad(status, 11),
|
|
131
|
+
pad(outcome.label, 11),
|
|
124
132
|
pad(trigger, 14),
|
|
125
133
|
pad(ctx, 4),
|
|
126
134
|
pad(phases, 8),
|
|
@@ -130,6 +138,10 @@ export async function historyCommand(opts) {
|
|
|
130
138
|
pad(date, 20),
|
|
131
139
|
pad(headline, 42),
|
|
132
140
|
].join(' '));
|
|
141
|
+
|
|
142
|
+
if (outcome.next_action) {
|
|
143
|
+
console.log(chalk.dim(` next: ${truncateLine(outcome.next_action, 148)}`));
|
|
144
|
+
}
|
|
133
145
|
});
|
|
134
146
|
|
|
135
147
|
console.log(chalk.dim(`\n${entries.length} run(s) shown`));
|
|
@@ -176,3 +188,9 @@ function formatHeadline(headline) {
|
|
|
176
188
|
if (normalized.length <= 40) return normalized;
|
|
177
189
|
return `${normalized.slice(0, 39)}…`;
|
|
178
190
|
}
|
|
191
|
+
|
|
192
|
+
function truncateLine(value, max) {
|
|
193
|
+
if (typeof value !== 'string') return '';
|
|
194
|
+
if (value.length <= max) return value;
|
|
195
|
+
return `${value.slice(0, max - 1)}…`;
|
|
196
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -5,8 +5,9 @@ import chalk from 'chalk';
|
|
|
5
5
|
import inquirer from 'inquirer';
|
|
6
6
|
import { CONFIG_FILE, LOCK_FILE, STATE_FILE } from '../lib/config.js';
|
|
7
7
|
import { generateVSCodeFiles } from '../lib/generate-vscode.js';
|
|
8
|
-
import { loadAllGovernedTemplates, loadGovernedTemplate, VALID_GOVERNED_TEMPLATE_IDS
|
|
8
|
+
import { loadAllGovernedTemplates, loadGovernedTemplate, VALID_GOVERNED_TEMPLATE_IDS } from '../lib/governed-templates.js';
|
|
9
9
|
import { normalizeWorkflowKit, VALID_PROMPT_TRANSPORTS } from '../lib/normalized-config.js';
|
|
10
|
+
import { buildGovernedPlanningArtifacts, interpolateTemplateContent } from '../lib/planning-artifacts.js';
|
|
10
11
|
|
|
11
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
const TEMPLATES_DIR = join(__dirname, '../templates');
|
|
@@ -47,24 +48,11 @@ function loadTemplates() {
|
|
|
47
48
|
return templates;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
function interpolateTemplateContent(contentTemplate, projectName) {
|
|
51
|
-
return contentTemplate.replaceAll('{{project_name}}', projectName);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
51
|
function appendPromptOverride(basePrompt, override) {
|
|
55
52
|
if (!override || !override.trim()) return basePrompt;
|
|
56
53
|
return `${basePrompt}\n\n---\n\n## Project-Type-Specific Guidance\n\n${override.trim()}\n`;
|
|
57
54
|
}
|
|
58
55
|
|
|
59
|
-
function appendAcceptanceHints(baseMatrix, acceptanceHints) {
|
|
60
|
-
if (!Array.isArray(acceptanceHints) || acceptanceHints.length === 0) {
|
|
61
|
-
return baseMatrix;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const hintLines = acceptanceHints.map((hint) => `- [ ] ${hint}`).join('\n');
|
|
65
|
-
return `${baseMatrix}\n\n## Template Guidance\n${hintLines}\n`;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
56
|
function findGitRoot(startDir) {
|
|
69
57
|
let current = resolve(startDir);
|
|
70
58
|
while (true) {
|
|
@@ -598,20 +586,6 @@ export async function resolveGovernedInitAnswers(opts, prompt = (questions) => i
|
|
|
598
586
|
};
|
|
599
587
|
}
|
|
600
588
|
|
|
601
|
-
function generateWorkflowKitPlaceholder(artifact, projectName) {
|
|
602
|
-
const filename = basename(artifact.path);
|
|
603
|
-
const title = filename.replace(/\.[^.]+$/, '').replace(/[-_]/g, ' ');
|
|
604
|
-
|
|
605
|
-
if (artifact.semantics === 'section_check' && artifact.semantics_config?.required_sections?.length) {
|
|
606
|
-
const sections = artifact.semantics_config.required_sections
|
|
607
|
-
.map(s => `${s}\n\n(Content here.)\n`)
|
|
608
|
-
.join('\n');
|
|
609
|
-
return `# ${title} — ${projectName}\n\n${sections}`;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
return `# ${title} — ${projectName}\n\n(Operator fills this in.)\n`;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
589
|
function cloneJsonCompatible(value) {
|
|
616
590
|
return value == null ? value : JSON.parse(JSON.stringify(value));
|
|
617
591
|
}
|
|
@@ -653,29 +627,6 @@ function buildScaffoldConfigFromTemplate(template, localDevRuntime, workflowKitC
|
|
|
653
627
|
};
|
|
654
628
|
}
|
|
655
629
|
|
|
656
|
-
const PHASE_DISPLAY_NAMES = Object.freeze({
|
|
657
|
-
qa: 'QA',
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
function formatPhaseDisplayName(phaseKey) {
|
|
661
|
-
if (PHASE_DISPLAY_NAMES[phaseKey]) {
|
|
662
|
-
return PHASE_DISPLAY_NAMES[phaseKey];
|
|
663
|
-
}
|
|
664
|
-
return phaseKey.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
function buildRoadmapPhaseTable(routing, roles) {
|
|
668
|
-
const rows = Object.entries(routing).map(([phaseKey, phaseConfig]) => {
|
|
669
|
-
const phaseName = formatPhaseDisplayName(phaseKey);
|
|
670
|
-
const entryRole = phaseConfig.entry_role;
|
|
671
|
-
const role = roles[entryRole];
|
|
672
|
-
const goal = role?.mandate || phaseName;
|
|
673
|
-
const status = phaseKey === Object.keys(routing)[0] ? 'In progress' : 'Pending';
|
|
674
|
-
return `| ${phaseName} | ${goal} | ${status} |`;
|
|
675
|
-
});
|
|
676
|
-
return `| Phase | Goal | Status |\n|-------|------|--------|\n${rows.join('\n')}\n`;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
630
|
function buildPlanningSummaryLines(template, workflowKitConfig) {
|
|
680
631
|
const lines = [
|
|
681
632
|
'PM_SIGNOFF.md / ROADMAP.md / SYSTEM_SPEC.md',
|
|
@@ -821,53 +772,20 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
821
772
|
}
|
|
822
773
|
|
|
823
774
|
// Planning artifacts
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
writeFileSync(join(dir, '.planning', 'RELEASE_NOTES.md'), `# Release Notes — ${projectName}\n\n## User Impact\n\n(QA fills this during the QA phase)\n\n## Verification Summary\n\n(QA fills this during the QA phase)\n\n## Upgrade Notes\n\n(QA fills this during the QA phase)\n\n## Known Issues\n\n(QA fills this during the QA phase)\n`);
|
|
835
|
-
for (const artifact of template.planning_artifacts) {
|
|
836
|
-
writeFileSync(
|
|
837
|
-
join(dir, '.planning', artifact.filename),
|
|
838
|
-
interpolateTemplateContent(artifact.content_template, projectName)
|
|
839
|
-
);
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// Workflow-kit custom artifacts — only scaffold files from explicit workflow_kit config
|
|
843
|
-
// that are not already handled by the default scaffold above
|
|
844
|
-
if (scaffoldWorkflowKitConfig && scaffoldWorkflowKitConfig.phases && typeof scaffoldWorkflowKitConfig.phases === 'object') {
|
|
845
|
-
const defaultScaffoldPaths = new Set([
|
|
846
|
-
'.planning/PM_SIGNOFF.md',
|
|
847
|
-
'.planning/ROADMAP.md',
|
|
848
|
-
'.planning/SYSTEM_SPEC.md',
|
|
849
|
-
'.planning/IMPLEMENTATION_NOTES.md',
|
|
850
|
-
'.planning/acceptance-matrix.md',
|
|
851
|
-
'.planning/ship-verdict.md',
|
|
852
|
-
'.planning/RELEASE_NOTES.md',
|
|
853
|
-
]);
|
|
854
|
-
|
|
855
|
-
for (const phaseConfig of Object.values(scaffoldWorkflowKitConfig.phases)) {
|
|
856
|
-
if (!Array.isArray(phaseConfig.artifacts)) continue;
|
|
857
|
-
for (const artifact of phaseConfig.artifacts) {
|
|
858
|
-
if (!artifact.path || defaultScaffoldPaths.has(artifact.path)) continue;
|
|
859
|
-
const absPath = join(dir, artifact.path);
|
|
860
|
-
if (existsSync(absPath)) continue;
|
|
861
|
-
|
|
862
|
-
// Ensure parent directory exists
|
|
863
|
-
const parentDir = dirname(absPath);
|
|
864
|
-
if (!existsSync(parentDir)) mkdirSync(parentDir, { recursive: true });
|
|
865
|
-
|
|
866
|
-
// Generate placeholder content based on semantics type
|
|
867
|
-
const content = generateWorkflowKitPlaceholder(artifact, projectName);
|
|
868
|
-
writeFileSync(absPath, content);
|
|
869
|
-
}
|
|
775
|
+
for (const artifact of buildGovernedPlanningArtifacts({
|
|
776
|
+
projectName,
|
|
777
|
+
routing,
|
|
778
|
+
roles,
|
|
779
|
+
template,
|
|
780
|
+
workflowKitConfig: scaffoldWorkflowKitConfig,
|
|
781
|
+
})) {
|
|
782
|
+
const absPath = join(dir, artifact.path);
|
|
783
|
+
if (artifact.source === 'workflow_kit' && existsSync(absPath)) {
|
|
784
|
+
continue;
|
|
870
785
|
}
|
|
786
|
+
const parentDir = dirname(absPath);
|
|
787
|
+
if (!existsSync(parentDir)) mkdirSync(parentDir, { recursive: true });
|
|
788
|
+
writeFileSync(absPath, artifact.content);
|
|
871
789
|
}
|
|
872
790
|
|
|
873
791
|
// TALK.md
|