popeye-cli 1.9.5 → 2.0.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/CHANGELOG.md +59 -0
- package/CONTRIBUTING.md +15 -1
- package/README.md +57 -0
- package/cheatsheet.md +65 -0
- package/dist/cli/commands/debug-context.d.ts +64 -0
- package/dist/cli/commands/debug-context.d.ts.map +1 -0
- package/dist/cli/commands/debug-context.js +221 -0
- package/dist/cli/commands/debug-context.js.map +1 -0
- package/dist/cli/commands/debug-prompts.d.ts +25 -0
- package/dist/cli/commands/debug-prompts.d.ts.map +1 -0
- package/dist/cli/commands/debug-prompts.js +80 -0
- package/dist/cli/commands/debug-prompts.js.map +1 -0
- package/dist/cli/commands/debug.d.ts +68 -0
- package/dist/cli/commands/debug.d.ts.map +1 -0
- package/dist/cli/commands/debug.js +543 -0
- package/dist/cli/commands/debug.js.map +1 -0
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +25 -0
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +2 -0
- package/dist/generators/all.js.map +1 -1
- package/dist/generators/templates/database-docker.d.ts.map +1 -1
- package/dist/generators/templates/database-docker.js +10 -0
- package/dist/generators/templates/database-docker.js.map +1 -1
- package/dist/generators/templates/fullstack.d.ts +4 -1
- package/dist/generators/templates/fullstack.d.ts.map +1 -1
- package/dist/generators/templates/fullstack.js +6 -2
- package/dist/generators/templates/fullstack.js.map +1 -1
- package/dist/pipeline/artifact-manager.d.ts +47 -0
- package/dist/pipeline/artifact-manager.d.ts.map +1 -0
- package/dist/pipeline/artifact-manager.js +251 -0
- package/dist/pipeline/artifact-manager.js.map +1 -0
- package/dist/pipeline/artifact-validators.d.ts +29 -0
- package/dist/pipeline/artifact-validators.d.ts.map +1 -0
- package/dist/pipeline/artifact-validators.js +173 -0
- package/dist/pipeline/artifact-validators.js.map +1 -0
- package/dist/pipeline/change-request.d.ts +47 -0
- package/dist/pipeline/change-request.d.ts.map +1 -0
- package/dist/pipeline/change-request.js +91 -0
- package/dist/pipeline/change-request.js.map +1 -0
- package/dist/pipeline/check-runner.d.ts +47 -0
- package/dist/pipeline/check-runner.d.ts.map +1 -0
- package/dist/pipeline/check-runner.js +417 -0
- package/dist/pipeline/check-runner.js.map +1 -0
- package/dist/pipeline/command-resolver.d.ts +9 -0
- package/dist/pipeline/command-resolver.d.ts.map +1 -0
- package/dist/pipeline/command-resolver.js +140 -0
- package/dist/pipeline/command-resolver.js.map +1 -0
- package/dist/pipeline/consensus/consensus-runner.d.ts +44 -0
- package/dist/pipeline/consensus/consensus-runner.d.ts.map +1 -0
- package/dist/pipeline/consensus/consensus-runner.js +212 -0
- package/dist/pipeline/consensus/consensus-runner.js.map +1 -0
- package/dist/pipeline/constitution.d.ts +45 -0
- package/dist/pipeline/constitution.d.ts.map +1 -0
- package/dist/pipeline/constitution.js +82 -0
- package/dist/pipeline/constitution.js.map +1 -0
- package/dist/pipeline/gate-engine.d.ts +55 -0
- package/dist/pipeline/gate-engine.d.ts.map +1 -0
- package/dist/pipeline/gate-engine.js +270 -0
- package/dist/pipeline/gate-engine.js.map +1 -0
- package/dist/pipeline/index.d.ts +26 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +35 -0
- package/dist/pipeline/index.js.map +1 -0
- package/dist/pipeline/migration.d.ts +15 -0
- package/dist/pipeline/migration.d.ts.map +1 -0
- package/dist/pipeline/migration.js +76 -0
- package/dist/pipeline/migration.js.map +1 -0
- package/dist/pipeline/orchestrator.d.ts +28 -0
- package/dist/pipeline/orchestrator.d.ts.map +1 -0
- package/dist/pipeline/orchestrator.js +238 -0
- package/dist/pipeline/orchestrator.js.map +1 -0
- package/dist/pipeline/packets/audit-report-builder.d.ts +11 -0
- package/dist/pipeline/packets/audit-report-builder.d.ts.map +1 -0
- package/dist/pipeline/packets/audit-report-builder.js +32 -0
- package/dist/pipeline/packets/audit-report-builder.js.map +1 -0
- package/dist/pipeline/packets/consensus-packet-builder.d.ts +35 -0
- package/dist/pipeline/packets/consensus-packet-builder.d.ts.map +1 -0
- package/dist/pipeline/packets/consensus-packet-builder.js +80 -0
- package/dist/pipeline/packets/consensus-packet-builder.js.map +1 -0
- package/dist/pipeline/packets/index.d.ts +12 -0
- package/dist/pipeline/packets/index.d.ts.map +1 -0
- package/dist/pipeline/packets/index.js +8 -0
- package/dist/pipeline/packets/index.js.map +1 -0
- package/dist/pipeline/packets/plan-packet-builder.d.ts +21 -0
- package/dist/pipeline/packets/plan-packet-builder.d.ts.map +1 -0
- package/dist/pipeline/packets/plan-packet-builder.js +27 -0
- package/dist/pipeline/packets/plan-packet-builder.js.map +1 -0
- package/dist/pipeline/packets/rca-packet-builder.d.ts +19 -0
- package/dist/pipeline/packets/rca-packet-builder.d.ts.map +1 -0
- package/dist/pipeline/packets/rca-packet-builder.js +22 -0
- package/dist/pipeline/packets/rca-packet-builder.js.map +1 -0
- package/dist/pipeline/phases/architecture.d.ts +7 -0
- package/dist/pipeline/phases/architecture.d.ts.map +1 -0
- package/dist/pipeline/phases/architecture.js +60 -0
- package/dist/pipeline/phases/architecture.js.map +1 -0
- package/dist/pipeline/phases/audit.d.ts +8 -0
- package/dist/pipeline/phases/audit.d.ts.map +1 -0
- package/dist/pipeline/phases/audit.js +144 -0
- package/dist/pipeline/phases/audit.js.map +1 -0
- package/dist/pipeline/phases/consensus-architecture.d.ts +7 -0
- package/dist/pipeline/phases/consensus-architecture.d.ts.map +1 -0
- package/dist/pipeline/phases/consensus-architecture.js +84 -0
- package/dist/pipeline/phases/consensus-architecture.js.map +1 -0
- package/dist/pipeline/phases/consensus-master-plan.d.ts +7 -0
- package/dist/pipeline/phases/consensus-master-plan.d.ts.map +1 -0
- package/dist/pipeline/phases/consensus-master-plan.js +81 -0
- package/dist/pipeline/phases/consensus-master-plan.js.map +1 -0
- package/dist/pipeline/phases/consensus-role-plans.d.ts +7 -0
- package/dist/pipeline/phases/consensus-role-plans.d.ts.map +1 -0
- package/dist/pipeline/phases/consensus-role-plans.js +85 -0
- package/dist/pipeline/phases/consensus-role-plans.js.map +1 -0
- package/dist/pipeline/phases/done.d.ts +7 -0
- package/dist/pipeline/phases/done.d.ts.map +1 -0
- package/dist/pipeline/phases/done.js +45 -0
- package/dist/pipeline/phases/done.js.map +1 -0
- package/dist/pipeline/phases/implementation.d.ts +8 -0
- package/dist/pipeline/phases/implementation.d.ts.map +1 -0
- package/dist/pipeline/phases/implementation.js +42 -0
- package/dist/pipeline/phases/implementation.js.map +1 -0
- package/dist/pipeline/phases/index.d.ts +20 -0
- package/dist/pipeline/phases/index.d.ts.map +1 -0
- package/dist/pipeline/phases/index.js +19 -0
- package/dist/pipeline/phases/index.js.map +1 -0
- package/dist/pipeline/phases/intake.d.ts +8 -0
- package/dist/pipeline/phases/intake.d.ts.map +1 -0
- package/dist/pipeline/phases/intake.js +40 -0
- package/dist/pipeline/phases/intake.js.map +1 -0
- package/dist/pipeline/phases/phase-context.d.ts +30 -0
- package/dist/pipeline/phases/phase-context.d.ts.map +1 -0
- package/dist/pipeline/phases/phase-context.js +33 -0
- package/dist/pipeline/phases/phase-context.js.map +1 -0
- package/dist/pipeline/phases/production-gate.d.ts +8 -0
- package/dist/pipeline/phases/production-gate.d.ts.map +1 -0
- package/dist/pipeline/phases/production-gate.js +84 -0
- package/dist/pipeline/phases/production-gate.js.map +1 -0
- package/dist/pipeline/phases/qa-validation.d.ts +7 -0
- package/dist/pipeline/phases/qa-validation.d.ts.map +1 -0
- package/dist/pipeline/phases/qa-validation.js +50 -0
- package/dist/pipeline/phases/qa-validation.js.map +1 -0
- package/dist/pipeline/phases/recovery-loop.d.ts +7 -0
- package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -0
- package/dist/pipeline/phases/recovery-loop.js +91 -0
- package/dist/pipeline/phases/recovery-loop.js.map +1 -0
- package/dist/pipeline/phases/review.d.ts +8 -0
- package/dist/pipeline/phases/review.d.ts.map +1 -0
- package/dist/pipeline/phases/review.js +127 -0
- package/dist/pipeline/phases/review.js.map +1 -0
- package/dist/pipeline/phases/role-planning.d.ts +7 -0
- package/dist/pipeline/phases/role-planning.d.ts.map +1 -0
- package/dist/pipeline/phases/role-planning.js +75 -0
- package/dist/pipeline/phases/role-planning.js.map +1 -0
- package/dist/pipeline/phases/stuck.d.ts +7 -0
- package/dist/pipeline/phases/stuck.d.ts.map +1 -0
- package/dist/pipeline/phases/stuck.js +51 -0
- package/dist/pipeline/phases/stuck.js.map +1 -0
- package/dist/pipeline/repo-snapshot.d.ts +24 -0
- package/dist/pipeline/repo-snapshot.d.ts.map +1 -0
- package/dist/pipeline/repo-snapshot.js +343 -0
- package/dist/pipeline/repo-snapshot.js.map +1 -0
- package/dist/pipeline/role-execution-adapter.d.ts +59 -0
- package/dist/pipeline/role-execution-adapter.d.ts.map +1 -0
- package/dist/pipeline/role-execution-adapter.js +159 -0
- package/dist/pipeline/role-execution-adapter.js.map +1 -0
- package/dist/pipeline/skill-loader.d.ts +34 -0
- package/dist/pipeline/skill-loader.d.ts.map +1 -0
- package/dist/pipeline/skill-loader.js +156 -0
- package/dist/pipeline/skill-loader.js.map +1 -0
- package/dist/pipeline/skills/defaults.d.ts +16 -0
- package/dist/pipeline/skills/defaults.d.ts.map +1 -0
- package/dist/pipeline/skills/defaults.js +189 -0
- package/dist/pipeline/skills/defaults.js.map +1 -0
- package/dist/pipeline/type-defs/artifacts.d.ts +202 -0
- package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -0
- package/dist/pipeline/type-defs/artifacts.js +66 -0
- package/dist/pipeline/type-defs/artifacts.js.map +1 -0
- package/dist/pipeline/type-defs/audit.d.ts +256 -0
- package/dist/pipeline/type-defs/audit.d.ts.map +1 -0
- package/dist/pipeline/type-defs/audit.js +54 -0
- package/dist/pipeline/type-defs/audit.js.map +1 -0
- package/dist/pipeline/type-defs/checks.d.ts +81 -0
- package/dist/pipeline/type-defs/checks.d.ts.map +1 -0
- package/dist/pipeline/type-defs/checks.js +38 -0
- package/dist/pipeline/type-defs/checks.js.map +1 -0
- package/dist/pipeline/type-defs/enums.d.ts +43 -0
- package/dist/pipeline/type-defs/enums.d.ts.map +1 -0
- package/dist/pipeline/type-defs/enums.js +55 -0
- package/dist/pipeline/type-defs/enums.js.map +1 -0
- package/dist/pipeline/type-defs/index.d.ts +12 -0
- package/dist/pipeline/type-defs/index.d.ts.map +1 -0
- package/dist/pipeline/type-defs/index.js +12 -0
- package/dist/pipeline/type-defs/index.js.map +1 -0
- package/dist/pipeline/type-defs/packets.d.ts +806 -0
- package/dist/pipeline/type-defs/packets.d.ts.map +1 -0
- package/dist/pipeline/type-defs/packets.js +109 -0
- package/dist/pipeline/type-defs/packets.js.map +1 -0
- package/dist/pipeline/type-defs/snapshot.d.ts +52 -0
- package/dist/pipeline/type-defs/snapshot.d.ts.map +1 -0
- package/dist/pipeline/type-defs/snapshot.js +35 -0
- package/dist/pipeline/type-defs/snapshot.js.map +1 -0
- package/dist/pipeline/type-defs/state.d.ts +449 -0
- package/dist/pipeline/type-defs/state.d.ts.map +1 -0
- package/dist/pipeline/type-defs/state.js +88 -0
- package/dist/pipeline/type-defs/state.js.map +1 -0
- package/dist/pipeline/types.d.ts +16 -0
- package/dist/pipeline/types.d.ts.map +1 -0
- package/dist/pipeline/types.js +16 -0
- package/dist/pipeline/types.js.map +1 -0
- package/dist/types/audit.d.ts +6 -6
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +48 -0
- package/dist/workflow/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/ARBITRATOR.md +137 -0
- package/skills/ARCHITECT.md +167 -0
- package/skills/AUDITOR.md +128 -0
- package/skills/AUDIT_REPORT_SCHEMA.md +20 -0
- package/skills/BACKEND_PROGRAMMER.md +95 -0
- package/skills/CONSENSUS_PACKET_SCHEMA.md +166 -0
- package/skills/DB_EXPERT.md +106 -0
- package/skills/DEBUGGER.md +286 -0
- package/skills/DISPATCHER.md +157 -0
- package/skills/FRONTEND_PROGRAMMER.md +84 -0
- package/skills/JOURNALIST.md +247 -0
- package/skills/MARKETING_EXPERT.md +23 -0
- package/skills/PHASE_GATE_ENGINE_SPEC.md +171 -0
- package/skills/PLAN_PACKET_SCHEMA.md +222 -0
- package/skills/POPEYE_CONSTITUTION.md +177 -0
- package/skills/POPEYE_FULL_AUTONOMY_PIPELINE.md +537 -0
- package/skills/PRODUCTION_READINESS_SCHEMA.md +19 -0
- package/skills/QA_TESTER.md +40 -0
- package/skills/RCA_PACKET_SCHEMA.md +22 -0
- package/skills/RELEASE_MANAGER.md +60 -0
- package/skills/REVIEWER.md +133 -0
- package/skills/SOCIAL_EXPERT.md +22 -0
- package/skills/UI_UX_SPECIALIST.md +22 -0
- package/skills/WEBSITE_PROGRAMMER.md +37 -0
- package/src/cli/commands/debug-context.ts +265 -0
- package/src/cli/commands/debug-prompts.ts +91 -0
- package/src/cli/commands/debug.ts +662 -0
- package/src/cli/commands/index.ts +1 -0
- package/src/cli/index.ts +2 -0
- package/src/cli/interactive.ts +27 -0
- package/src/generators/all.ts +2 -0
- package/src/generators/templates/database-docker.ts +10 -0
- package/src/generators/templates/fullstack.ts +6 -2
- package/src/pipeline/artifact-manager.ts +339 -0
- package/src/pipeline/artifact-validators.ts +224 -0
- package/src/pipeline/change-request.ts +119 -0
- package/src/pipeline/check-runner.ts +504 -0
- package/src/pipeline/command-resolver.ts +168 -0
- package/src/pipeline/consensus/consensus-runner.ts +317 -0
- package/src/pipeline/constitution.ts +109 -0
- package/src/pipeline/gate-engine.ts +347 -0
- package/src/pipeline/index.ts +82 -0
- package/src/pipeline/migration.ts +91 -0
- package/src/pipeline/orchestrator.ts +314 -0
- package/src/pipeline/packets/audit-report-builder.ts +47 -0
- package/src/pipeline/packets/consensus-packet-builder.ts +112 -0
- package/src/pipeline/packets/index.ts +15 -0
- package/src/pipeline/packets/plan-packet-builder.ts +52 -0
- package/src/pipeline/packets/rca-packet-builder.ts +38 -0
- package/src/pipeline/phases/architecture.ts +73 -0
- package/src/pipeline/phases/audit.ts +193 -0
- package/src/pipeline/phases/consensus-architecture.ts +104 -0
- package/src/pipeline/phases/consensus-master-plan.ts +100 -0
- package/src/pipeline/phases/consensus-role-plans.ts +105 -0
- package/src/pipeline/phases/done.ts +68 -0
- package/src/pipeline/phases/implementation.ts +48 -0
- package/src/pipeline/phases/index.ts +21 -0
- package/src/pipeline/phases/intake.ts +54 -0
- package/src/pipeline/phases/phase-context.ts +86 -0
- package/src/pipeline/phases/production-gate.ts +113 -0
- package/src/pipeline/phases/qa-validation.ts +63 -0
- package/src/pipeline/phases/recovery-loop.ts +118 -0
- package/src/pipeline/phases/review.ts +149 -0
- package/src/pipeline/phases/role-planning.ts +92 -0
- package/src/pipeline/phases/stuck.ts +62 -0
- package/src/pipeline/repo-snapshot.ts +395 -0
- package/src/pipeline/role-execution-adapter.ts +238 -0
- package/src/pipeline/skill-loader.ts +192 -0
- package/src/pipeline/skills/defaults.ts +215 -0
- package/src/pipeline/type-defs/artifacts.ts +81 -0
- package/src/pipeline/type-defs/audit.ts +67 -0
- package/src/pipeline/type-defs/checks.ts +47 -0
- package/src/pipeline/type-defs/enums.ts +62 -0
- package/src/pipeline/type-defs/index.ts +12 -0
- package/src/pipeline/type-defs/packets.ts +131 -0
- package/src/pipeline/type-defs/snapshot.ts +55 -0
- package/src/pipeline/type-defs/state.ts +165 -0
- package/src/pipeline/types.ts +16 -0
- package/src/workflow/index.ts +48 -0
- package/tests/cli/commands/debug.test.ts +376 -0
- package/tests/pipeline/artifact-manager.test.ts +183 -0
- package/tests/pipeline/artifact-validators.test.ts +207 -0
- package/tests/pipeline/change-request.test.ts +180 -0
- package/tests/pipeline/check-runner.test.ts +157 -0
- package/tests/pipeline/command-resolver.test.ts +159 -0
- package/tests/pipeline/consensus-runner.test.ts +206 -0
- package/tests/pipeline/consensus-scoring.test.ts +163 -0
- package/tests/pipeline/constitution.test.ts +122 -0
- package/tests/pipeline/gate-engine.test.ts +195 -0
- package/tests/pipeline/migration.test.ts +133 -0
- package/tests/pipeline/orchestrator.test.ts +614 -0
- package/tests/pipeline/packets/builders.test.ts +347 -0
- package/tests/pipeline/repo-snapshot.test.ts +189 -0
- package/tests/pipeline/role-execution-adapter.test.ts +299 -0
- package/tests/pipeline/skill-loader.test.ts +186 -0
- package/tests/pipeline/start-env-checks.test.ts +123 -0
- package/tests/pipeline/types.test.ts +156 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Resolver — detects project-type-specific build/test/lint/typecheck
|
|
3
|
+
* commands from a RepoSnapshot. Used by CheckRunner and ProductionGate.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RepoSnapshot, ResolvedCommands } from './types.js';
|
|
7
|
+
|
|
8
|
+
// ─── Project Type Detection ──────────────────────────────
|
|
9
|
+
|
|
10
|
+
export type ProjectType = 'node' | 'python' | 'mixed' | 'unknown';
|
|
11
|
+
|
|
12
|
+
export function detectProjectType(snapshot: RepoSnapshot): ProjectType {
|
|
13
|
+
const hasNode = snapshot.config_files.some((c) => c.type === 'package.json');
|
|
14
|
+
const hasPython = snapshot.config_files.some(
|
|
15
|
+
(c) => c.type === 'pyproject.toml' || c.type === 'requirements.txt' || c.type === 'setup.py',
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
if (hasNode && hasPython) return 'mixed';
|
|
19
|
+
if (hasNode) return 'node';
|
|
20
|
+
if (hasPython) return 'python';
|
|
21
|
+
return 'unknown';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ─── Command Resolution ──────────────────────────────────
|
|
25
|
+
|
|
26
|
+
export function resolveCommands(
|
|
27
|
+
snapshot: RepoSnapshot,
|
|
28
|
+
overrides?: Partial<ResolvedCommands>,
|
|
29
|
+
): ResolvedCommands {
|
|
30
|
+
const projectType = detectProjectType(snapshot);
|
|
31
|
+
const pm = snapshot.package_manager ?? 'npm';
|
|
32
|
+
const scripts = snapshot.scripts;
|
|
33
|
+
|
|
34
|
+
let resolved: ResolvedCommands;
|
|
35
|
+
|
|
36
|
+
switch (projectType) {
|
|
37
|
+
case 'node':
|
|
38
|
+
resolved = resolveNodeCommands(pm, scripts, snapshot);
|
|
39
|
+
break;
|
|
40
|
+
case 'python':
|
|
41
|
+
resolved = resolvePythonCommands(snapshot);
|
|
42
|
+
break;
|
|
43
|
+
case 'mixed':
|
|
44
|
+
// Prefer Node commands, augment with Python where Node is missing
|
|
45
|
+
resolved = resolveNodeCommands(pm, scripts, snapshot);
|
|
46
|
+
if (!resolved.test) {
|
|
47
|
+
const pyResolved = resolvePythonCommands(snapshot);
|
|
48
|
+
resolved.test = pyResolved.test;
|
|
49
|
+
}
|
|
50
|
+
break;
|
|
51
|
+
default:
|
|
52
|
+
resolved = { resolved_from: 'none' };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Apply overrides
|
|
56
|
+
if (overrides) {
|
|
57
|
+
if (overrides.build) resolved.build = overrides.build;
|
|
58
|
+
if (overrides.test) resolved.test = overrides.test;
|
|
59
|
+
if (overrides.lint) resolved.lint = overrides.lint;
|
|
60
|
+
if (overrides.typecheck) resolved.typecheck = overrides.typecheck;
|
|
61
|
+
if (overrides.migrations) resolved.migrations = overrides.migrations;
|
|
62
|
+
if (overrides.start) resolved.start = overrides.start;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return resolved;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── Node Resolution ─────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
function resolveNodeCommands(
|
|
71
|
+
pm: string,
|
|
72
|
+
scripts: Record<string, string>,
|
|
73
|
+
snapshot: RepoSnapshot,
|
|
74
|
+
): ResolvedCommands {
|
|
75
|
+
const run = pm === 'yarn' ? 'yarn' : pm === 'pnpm' ? 'pnpm' : `${pm} run`;
|
|
76
|
+
const npx = pm === 'pnpm' ? 'pnpm exec' : pm === 'yarn' ? 'yarn' : 'npx';
|
|
77
|
+
|
|
78
|
+
const resolved: ResolvedCommands = {
|
|
79
|
+
resolved_from: 'package.json',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Build
|
|
83
|
+
if (scripts.build) {
|
|
84
|
+
resolved.build = `${run} build`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Test
|
|
88
|
+
if (scripts.test) {
|
|
89
|
+
resolved.test = `${run} test`;
|
|
90
|
+
} else if (snapshot.test_framework === 'vitest') {
|
|
91
|
+
resolved.test = `${npx} vitest run`;
|
|
92
|
+
} else if (snapshot.test_framework === 'jest') {
|
|
93
|
+
resolved.test = `${npx} jest`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Lint
|
|
97
|
+
if (scripts.lint) {
|
|
98
|
+
resolved.lint = `${run} lint`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Typecheck
|
|
102
|
+
if (scripts.typecheck) {
|
|
103
|
+
resolved.typecheck = `${run} typecheck`;
|
|
104
|
+
} else if (snapshot.languages_detected.includes('typescript')) {
|
|
105
|
+
resolved.typecheck = `${npx} tsc --noEmit`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Migrations
|
|
109
|
+
const hasPrisma = snapshot.config_files.some(
|
|
110
|
+
(c) => c.type === 'prisma/schema.prisma',
|
|
111
|
+
);
|
|
112
|
+
if (hasPrisma) {
|
|
113
|
+
resolved.migrations = `${npx} prisma migrate deploy`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Start
|
|
117
|
+
if (scripts.start) {
|
|
118
|
+
resolved.start = `${run} start`;
|
|
119
|
+
} else if (scripts.dev) {
|
|
120
|
+
resolved.start = `${run} dev`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return resolved;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ─── Python Resolution ───────────────────────────────────
|
|
127
|
+
|
|
128
|
+
function resolvePythonCommands(snapshot: RepoSnapshot): ResolvedCommands {
|
|
129
|
+
const resolved: ResolvedCommands = {
|
|
130
|
+
resolved_from: snapshot.config_files
|
|
131
|
+
.find((c) => c.type === 'pyproject.toml' || c.type === 'requirements.txt')
|
|
132
|
+
?.path ?? 'python-defaults',
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Test
|
|
136
|
+
if (snapshot.test_framework === 'pytest') {
|
|
137
|
+
resolved.test = 'pytest tests/';
|
|
138
|
+
} else {
|
|
139
|
+
resolved.test = 'pytest tests/'; // default for Python
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Lint
|
|
143
|
+
const hasPyproject = snapshot.config_files.some((c) => c.type === 'pyproject.toml');
|
|
144
|
+
if (hasPyproject) {
|
|
145
|
+
resolved.lint = 'ruff check .';
|
|
146
|
+
} else {
|
|
147
|
+
resolved.lint = 'flake8 src/';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Typecheck
|
|
151
|
+
if (snapshot.languages_detected.includes('python')) {
|
|
152
|
+
resolved.typecheck = 'mypy src/';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Build
|
|
156
|
+
resolved.build = 'python -m build';
|
|
157
|
+
|
|
158
|
+
// Migrations
|
|
159
|
+
const hasAlembic = snapshot.config_files.some((c) => c.type === 'alembic.ini');
|
|
160
|
+
if (hasAlembic) {
|
|
161
|
+
resolved.migrations = 'alembic upgrade head';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Start
|
|
165
|
+
resolved.start = 'uvicorn main:app --host 0.0.0.0 --port 8000';
|
|
166
|
+
|
|
167
|
+
return resolved;
|
|
168
|
+
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consensus Runner — adapter layer between structured packets
|
|
3
|
+
* and existing consensus machinery.
|
|
4
|
+
*
|
|
5
|
+
* Two modes (P1-D):
|
|
6
|
+
* 1. Independent Review (DEFAULT): N reviewers review simultaneously,
|
|
7
|
+
* no reviewer sees other reviewers' output.
|
|
8
|
+
* 2. Iterative Consensus (optional): for recovery plan iteration.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
PlanPacket,
|
|
15
|
+
ConsensusPacket,
|
|
16
|
+
ReviewerVote,
|
|
17
|
+
} from '../types.js';
|
|
18
|
+
import type { GateDefinition } from '../gate-engine.js';
|
|
19
|
+
import { buildConsensusPacket } from '../packets/consensus-packet-builder.js';
|
|
20
|
+
import type { ConsensusRules } from '../packets/consensus-packet-builder.js';
|
|
21
|
+
|
|
22
|
+
// Re-use existing consensus infrastructure
|
|
23
|
+
import { iterateUntilConsensus } from '../../workflow/consensus.js';
|
|
24
|
+
import type { ConsensusConfig, ConsensusResult } from '../../types/consensus.js';
|
|
25
|
+
|
|
26
|
+
// ─── Types ───────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
export interface ConsensusRunnerConfig {
|
|
29
|
+
mode: 'independent' | 'iterative';
|
|
30
|
+
minReviewers: number;
|
|
31
|
+
threshold: number;
|
|
32
|
+
quorum: number;
|
|
33
|
+
projectDir: string;
|
|
34
|
+
consensusConfig?: Partial<ConsensusConfig>;
|
|
35
|
+
/** Provider configurations for multi-LLM review */
|
|
36
|
+
reviewerProviders?: ReviewerProviderConfig[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ReviewerProviderConfig {
|
|
40
|
+
provider: string; // 'openai' | 'gemini' | 'grok'
|
|
41
|
+
model: string;
|
|
42
|
+
temperature: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const DEFAULT_PROVIDERS: ReviewerProviderConfig[] = [
|
|
46
|
+
{ provider: 'openai', model: 'gpt-4o', temperature: 0.3 },
|
|
47
|
+
{ provider: 'gemini', model: 'gemini-2.0-flash', temperature: 0.3 },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
// ─── Consensus Runner ────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
export class ConsensusRunner {
|
|
53
|
+
private readonly config: ConsensusRunnerConfig;
|
|
54
|
+
|
|
55
|
+
constructor(config: ConsensusRunnerConfig) {
|
|
56
|
+
this.config = config;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Run structured consensus on a plan packet */
|
|
60
|
+
async runStructuredConsensus(
|
|
61
|
+
planPacket: PlanPacket,
|
|
62
|
+
gateDefinition: GateDefinition,
|
|
63
|
+
): Promise<ConsensusPacket> {
|
|
64
|
+
const rules: ConsensusRules = {
|
|
65
|
+
threshold: gateDefinition.consensusThreshold ?? this.config.threshold,
|
|
66
|
+
quorum: this.config.quorum,
|
|
67
|
+
min_reviewers: gateDefinition.minReviewers ?? this.config.minReviewers,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let votes: ReviewerVote[];
|
|
71
|
+
|
|
72
|
+
if (this.config.mode === 'independent') {
|
|
73
|
+
votes = await this.runIndependentReview(planPacket);
|
|
74
|
+
} else {
|
|
75
|
+
votes = await this.runIterativeReview(planPacket);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Build consensus packet from votes
|
|
79
|
+
const packet = buildConsensusPacket({
|
|
80
|
+
planPacketRef: {
|
|
81
|
+
artifact_id: planPacket.metadata.packet_id,
|
|
82
|
+
path: '',
|
|
83
|
+
sha256: '',
|
|
84
|
+
version: planPacket.metadata.version,
|
|
85
|
+
type: 'consensus',
|
|
86
|
+
},
|
|
87
|
+
votes,
|
|
88
|
+
rules,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return packet;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Independent review: spawn N reviewers, each reviews independently */
|
|
95
|
+
async runIndependentReview(planPacket: PlanPacket): Promise<ReviewerVote[]> {
|
|
96
|
+
const providers = this.config.reviewerProviders ?? DEFAULT_PROVIDERS;
|
|
97
|
+
const numReviewers = Math.max(
|
|
98
|
+
this.config.minReviewers,
|
|
99
|
+
providers.length,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Build the review prompt from the plan packet
|
|
103
|
+
const prompt = buildReviewPrompt(planPacket);
|
|
104
|
+
const promptHash = createHash('sha256').update(prompt).digest('hex');
|
|
105
|
+
|
|
106
|
+
// Spawn reviewers in parallel
|
|
107
|
+
const reviewPromises: Promise<ReviewerVote>[] = [];
|
|
108
|
+
for (let i = 0; i < numReviewers; i++) {
|
|
109
|
+
const provider = providers[i % providers.length];
|
|
110
|
+
reviewPromises.push(
|
|
111
|
+
this.spawnSingleReviewer(
|
|
112
|
+
prompt,
|
|
113
|
+
promptHash,
|
|
114
|
+
provider,
|
|
115
|
+
`reviewer-${provider.provider}-${i}`,
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return Promise.all(reviewPromises);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Iterative review: wraps existing iterateUntilConsensus */
|
|
124
|
+
async runIterativeReview(planPacket: PlanPacket): Promise<ReviewerVote[]> {
|
|
125
|
+
const prompt = buildReviewPrompt(planPacket);
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const result = await iterateUntilConsensus(
|
|
129
|
+
prompt,
|
|
130
|
+
`Phase: ${planPacket.metadata.phase}`,
|
|
131
|
+
{
|
|
132
|
+
projectDir: this.config.projectDir,
|
|
133
|
+
config: this.config.consensusConfig,
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Convert legacy result to ReviewerVote format
|
|
138
|
+
const vote: ReviewerVote = {
|
|
139
|
+
reviewer_id: 'iterative-reviewer',
|
|
140
|
+
provider: 'openai',
|
|
141
|
+
model: this.config.consensusConfig?.openaiModel ?? 'gpt-4o',
|
|
142
|
+
temperature: this.config.consensusConfig?.temperature ?? 0.3,
|
|
143
|
+
prompt_hash: createHash('sha256').update(prompt).digest('hex'),
|
|
144
|
+
vote: result.approved ? 'APPROVE' : 'REJECT',
|
|
145
|
+
confidence: result.finalScore ?? 0.5,
|
|
146
|
+
blocking_issues: result.finalConcerns ?? [],
|
|
147
|
+
suggestions: result.finalRecommendations ?? [],
|
|
148
|
+
evidence_refs: [],
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return [vote];
|
|
152
|
+
} catch {
|
|
153
|
+
return [{
|
|
154
|
+
reviewer_id: 'iterative-reviewer-error',
|
|
155
|
+
provider: 'openai',
|
|
156
|
+
model: 'unknown',
|
|
157
|
+
temperature: 0,
|
|
158
|
+
prompt_hash: '',
|
|
159
|
+
vote: 'REJECT',
|
|
160
|
+
confidence: 0,
|
|
161
|
+
blocking_issues: ['Iterative consensus failed'],
|
|
162
|
+
suggestions: [],
|
|
163
|
+
evidence_refs: [],
|
|
164
|
+
}];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Spawn a single independent reviewer */
|
|
169
|
+
private async spawnSingleReviewer(
|
|
170
|
+
prompt: string,
|
|
171
|
+
promptHash: string,
|
|
172
|
+
provider: ReviewerProviderConfig,
|
|
173
|
+
reviewerId: string,
|
|
174
|
+
): Promise<ReviewerVote> {
|
|
175
|
+
try {
|
|
176
|
+
const result = await this.callProviderForReview(prompt, provider);
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
reviewer_id: reviewerId,
|
|
180
|
+
provider: provider.provider,
|
|
181
|
+
model: provider.model,
|
|
182
|
+
temperature: provider.temperature,
|
|
183
|
+
prompt_hash: promptHash,
|
|
184
|
+
vote: result.approved ? 'APPROVE' : 'REJECT',
|
|
185
|
+
confidence: result.confidence,
|
|
186
|
+
blocking_issues: result.blockingIssues,
|
|
187
|
+
suggestions: result.suggestions,
|
|
188
|
+
evidence_refs: [],
|
|
189
|
+
};
|
|
190
|
+
} catch {
|
|
191
|
+
return {
|
|
192
|
+
reviewer_id: reviewerId,
|
|
193
|
+
provider: provider.provider,
|
|
194
|
+
model: provider.model,
|
|
195
|
+
temperature: provider.temperature,
|
|
196
|
+
prompt_hash: promptHash,
|
|
197
|
+
vote: 'REJECT',
|
|
198
|
+
confidence: 0,
|
|
199
|
+
blocking_issues: [`Review failed for ${provider.provider}`],
|
|
200
|
+
suggestions: [],
|
|
201
|
+
evidence_refs: [],
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Call the appropriate provider adapter for a review */
|
|
207
|
+
private async callProviderForReview(
|
|
208
|
+
prompt: string,
|
|
209
|
+
provider: ReviewerProviderConfig,
|
|
210
|
+
): Promise<ProviderReviewResult> {
|
|
211
|
+
switch (provider.provider) {
|
|
212
|
+
case 'openai': {
|
|
213
|
+
const { requestConsensus } = await import('../../adapters/openai.js');
|
|
214
|
+
const result = await requestConsensus(prompt, '', {
|
|
215
|
+
openaiModel: provider.model,
|
|
216
|
+
temperature: provider.temperature,
|
|
217
|
+
} as Partial<ConsensusConfig>);
|
|
218
|
+
return parseConsensusResult(result);
|
|
219
|
+
}
|
|
220
|
+
case 'gemini': {
|
|
221
|
+
const { requestConsensus } = await import('../../adapters/gemini.js');
|
|
222
|
+
const result = await requestConsensus(prompt, '', {
|
|
223
|
+
model: provider.model as never,
|
|
224
|
+
temperature: provider.temperature,
|
|
225
|
+
});
|
|
226
|
+
return parseConsensusResult(result);
|
|
227
|
+
}
|
|
228
|
+
case 'grok': {
|
|
229
|
+
const { requestConsensus } = await import('../../adapters/grok.js');
|
|
230
|
+
const result = await requestConsensus(prompt, '', {
|
|
231
|
+
model: provider.model,
|
|
232
|
+
temperature: provider.temperature,
|
|
233
|
+
});
|
|
234
|
+
return parseConsensusResult(result);
|
|
235
|
+
}
|
|
236
|
+
default:
|
|
237
|
+
throw new Error(`Unknown provider: ${provider.provider}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ─── Helper Types ────────────────────────────────────────
|
|
243
|
+
|
|
244
|
+
interface ProviderReviewResult {
|
|
245
|
+
approved: boolean;
|
|
246
|
+
confidence: number;
|
|
247
|
+
blockingIssues: string[];
|
|
248
|
+
suggestions: string[];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function parseConsensusResult(result: ConsensusResult): ProviderReviewResult {
|
|
252
|
+
return {
|
|
253
|
+
approved: result.approved,
|
|
254
|
+
confidence: result.score / 100, // score is 0-100, confidence is 0-1
|
|
255
|
+
blockingIssues: result.concerns ?? [],
|
|
256
|
+
suggestions: result.recommendations ?? [],
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ─── Prompt Builder ──────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
export function buildReviewPrompt(planPacket: PlanPacket): string {
|
|
263
|
+
const lines: string[] = [
|
|
264
|
+
`# Independent Plan Review`,
|
|
265
|
+
``,
|
|
266
|
+
`## Phase: ${planPacket.metadata.phase}`,
|
|
267
|
+
`## Submitted by: ${planPacket.metadata.submitted_by}`,
|
|
268
|
+
`## Version: ${planPacket.metadata.version}`,
|
|
269
|
+
``,
|
|
270
|
+
`## Acceptance Criteria`,
|
|
271
|
+
...planPacket.acceptance_criteria.map((c) => `- ${c}`),
|
|
272
|
+
``,
|
|
273
|
+
`## Constraints`,
|
|
274
|
+
...planPacket.constraints.map((c) => `- [${c.type}] ${c.description}`),
|
|
275
|
+
``,
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
if (planPacket.open_questions?.length) {
|
|
279
|
+
lines.push(`## Open Questions`);
|
|
280
|
+
lines.push(...planPacket.open_questions.map((q) => `- ${q}`));
|
|
281
|
+
lines.push('');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
lines.push(
|
|
285
|
+
`## Review Instructions`,
|
|
286
|
+
``,
|
|
287
|
+
`You are an independent reviewer. Evaluate this plan for:`,
|
|
288
|
+
`1. Completeness — are all required artifacts defined?`,
|
|
289
|
+
`2. Consistency — do acceptance criteria match constraints?`,
|
|
290
|
+
`3. Feasibility — can this be implemented as described?`,
|
|
291
|
+
`4. Constitution compliance — does it follow governance rules?`,
|
|
292
|
+
``,
|
|
293
|
+
`Respond with:`,
|
|
294
|
+
`- APPROVE, REJECT, or CONDITIONAL`,
|
|
295
|
+
`- Confidence score (0-1)`,
|
|
296
|
+
`- Blocking issues (if any)`,
|
|
297
|
+
`- Suggestions for improvement`,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
return lines.join('\n');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ─── Factory ─────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
export function createConsensusRunner(
|
|
306
|
+
projectDir: string,
|
|
307
|
+
consensusConfig?: Partial<ConsensusConfig>,
|
|
308
|
+
): ConsensusRunner {
|
|
309
|
+
return new ConsensusRunner({
|
|
310
|
+
mode: 'independent',
|
|
311
|
+
minReviewers: 2,
|
|
312
|
+
threshold: 0.95,
|
|
313
|
+
quorum: 2,
|
|
314
|
+
projectDir,
|
|
315
|
+
consensusConfig,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constitution management — artifact creation, hashing, and verification.
|
|
3
|
+
* The constitution (skills/POPEYE_CONSTITUTION.md) is the immutable governance
|
|
4
|
+
* document for the pipeline. This module ensures it is tracked as an artifact
|
|
5
|
+
* and its integrity is verified at every gate.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
|
|
12
|
+
import type { ArtifactEntry, PipelineState } from './types.js';
|
|
13
|
+
import type { ArtifactManager } from './artifact-manager.js';
|
|
14
|
+
|
|
15
|
+
// ─── Constants ───────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const CONSTITUTION_FILENAME = 'POPEYE_CONSTITUTION.md';
|
|
18
|
+
const SKILLS_DIR = 'skills';
|
|
19
|
+
|
|
20
|
+
// ─── Hash Computation ────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Compute SHA-256 hash of constitution file content.
|
|
24
|
+
*
|
|
25
|
+
* Args:
|
|
26
|
+
* projectDir: Root project directory containing skills/ folder.
|
|
27
|
+
*
|
|
28
|
+
* Returns:
|
|
29
|
+
* Hex-encoded SHA-256 hash, or empty string if file not found.
|
|
30
|
+
*/
|
|
31
|
+
export function computeConstitutionHash(projectDir: string): string {
|
|
32
|
+
const constitutionPath = join(projectDir, SKILLS_DIR, CONSTITUTION_FILENAME);
|
|
33
|
+
if (!existsSync(constitutionPath)) {
|
|
34
|
+
return '';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const content = readFileSync(constitutionPath, 'utf-8');
|
|
38
|
+
return createHash('sha256').update(content, 'utf-8').digest('hex');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─── Artifact Creation ───────────────────────────────────
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create a constitution artifact from the skills/POPEYE_CONSTITUTION.md file.
|
|
45
|
+
*
|
|
46
|
+
* Args:
|
|
47
|
+
* projectDir: Root project directory.
|
|
48
|
+
* artifactManager: The artifact manager instance.
|
|
49
|
+
*
|
|
50
|
+
* Returns:
|
|
51
|
+
* The created ArtifactEntry, or null if constitution file not found.
|
|
52
|
+
*/
|
|
53
|
+
export function createConstitutionArtifact(
|
|
54
|
+
projectDir: string,
|
|
55
|
+
artifactManager: ArtifactManager,
|
|
56
|
+
): ArtifactEntry | null {
|
|
57
|
+
const constitutionPath = join(projectDir, SKILLS_DIR, CONSTITUTION_FILENAME);
|
|
58
|
+
if (!existsSync(constitutionPath)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const content = readFileSync(constitutionPath, 'utf-8');
|
|
63
|
+
return artifactManager.createAndStoreText(
|
|
64
|
+
'constitution',
|
|
65
|
+
content,
|
|
66
|
+
'INTAKE',
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── Verification ────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Verify the constitution file has not been modified since pipeline start.
|
|
74
|
+
* Compares current file hash against the hash stored in pipeline state.
|
|
75
|
+
*
|
|
76
|
+
* Args:
|
|
77
|
+
* pipeline: Current pipeline state (contains constitutionHash).
|
|
78
|
+
* projectDir: Root project directory.
|
|
79
|
+
*
|
|
80
|
+
* Returns:
|
|
81
|
+
* Object with valid=true if hash matches, or valid=false with reason.
|
|
82
|
+
*/
|
|
83
|
+
export function verifyConstitution(
|
|
84
|
+
pipeline: PipelineState,
|
|
85
|
+
projectDir: string,
|
|
86
|
+
): { valid: boolean; reason?: string } {
|
|
87
|
+
// If no hash stored yet (pre-INTAKE), skip verification
|
|
88
|
+
if (!pipeline.constitutionHash) {
|
|
89
|
+
return { valid: true };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const currentHash = computeConstitutionHash(projectDir);
|
|
93
|
+
|
|
94
|
+
if (!currentHash) {
|
|
95
|
+
return {
|
|
96
|
+
valid: false,
|
|
97
|
+
reason: 'Constitution file not found — may have been deleted',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (currentHash !== pipeline.constitutionHash) {
|
|
102
|
+
return {
|
|
103
|
+
valid: false,
|
|
104
|
+
reason: 'Constitution has been modified since pipeline start',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { valid: true };
|
|
109
|
+
}
|