@surlymochan/evalid 0.0.0-stage → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/LICENSE +21 -0
  3. package/README.md +315 -2
  4. package/RELEASE_NOTES.md +102 -0
  5. package/atoms/registry.json +758 -0
  6. package/bin/xflow.js +7208 -0
  7. package/docs/adoption/README.md +128 -0
  8. package/docs/adoption/openflow-release-hardening.md +61 -0
  9. package/docs/benchmark-openflow-vs-gstack-superpowers-openspec-2026-05-25.md +81 -0
  10. package/docs/compatibility.md +52 -0
  11. package/docs/competitive-benchmark.md +108 -0
  12. package/docs/corps-operator-guide.md +75 -0
  13. package/docs/demo-proof.md +126 -0
  14. package/docs/examples-gallery.md +115 -0
  15. package/docs/fixtures/tracker-item.json +7 -0
  16. package/docs/goal-vs-codex.md +129 -0
  17. package/docs/gstack-comparison.md +88 -0
  18. package/docs/install-upgrade.md +133 -0
  19. package/docs/integrations.md +49 -0
  20. package/docs/launch-demo.md +123 -0
  21. package/docs/launch-dossier.md +180 -0
  22. package/docs/methodology.md +149 -0
  23. package/docs/npm-publish-handoff.md +83 -0
  24. package/docs/openspec-migration.md +106 -0
  25. package/docs/public-benchmark.md +68 -0
  26. package/docs/public-release.md +145 -0
  27. package/docs/quality-assessment.md +139 -0
  28. package/docs/quickstart.md +156 -0
  29. package/docs/reviewer-guide.md +43 -0
  30. package/docs/spec-kit-benchmark.md +73 -0
  31. package/docs/super-assistant-comparison.md +106 -0
  32. package/docs/superpowers-comparison.md +82 -0
  33. package/docs/team-adoption.md +123 -0
  34. package/docs/tooling-matrix.md +91 -0
  35. package/docs/walkthrough.md +76 -0
  36. package/docs/workflow-manual.md +1125 -0
  37. package/package.json +83 -4
  38. package/prompts/pencil-draft.md +46 -0
  39. package/prompts/pencil-refine.md +46 -0
  40. package/prompts/phase-artifact.md +24 -0
  41. package/prompts/phase-execution.md +32 -0
  42. package/schemas/competitor-reconstruction-contract.schema.json +91 -0
  43. package/schemas/llm-design-review-aggregate.schema.json +83 -0
  44. package/schemas/visual-benchmark.schema.json +978 -0
  45. package/schemas/workflow.schema.json +100 -0
  46. package/scripts/run-tests.mjs +23 -0
  47. package/scripts/takein_digest.py +139 -0
  48. package/specs/README.md +118 -0
  49. package/specs/architecture.md +36 -0
  50. package/specs/product.md +33 -0
  51. package/specs/workflow.md +349 -0
  52. package/src/agent_team/atoms/codex-app-bridge.js +8 -0
  53. package/src/agent_team/atoms/design/competitor-reconstruction-review.js +44 -0
  54. package/src/agent_team/atoms/design/contract-freeze.js +8 -0
  55. package/src/agent_team/atoms/design/generation-contract.js +442 -0
  56. package/src/agent_team/atoms/design/reconstruction-pack.js +246 -0
  57. package/src/agent_team/atoms/design/reference-surface-lock.js +49 -0
  58. package/src/agent_team/atoms/patch-challenge.js +19 -0
  59. package/src/agent_team/atoms/pencil/accept.js +9 -0
  60. package/src/agent_team/atoms/pencil/draft.js +365 -0
  61. package/src/agent_team/atoms/pencil/refine.js +229 -0
  62. package/src/agent_team/atoms/phase-run-dispatch.js +8 -0
  63. package/src/agent_team/atoms/team-run.js +380 -0
  64. package/src/agent_team/atoms/visual/aesthetic-review.js +231 -0
  65. package/src/agent_team/atoms/visual/benchmark-repair-plan.js +291 -0
  66. package/src/agent_team/atoms/visual/benchmark.js +1652 -0
  67. package/src/agent_team/atoms/visual/design-selection.js +23 -0
  68. package/src/agent_team/atoms/visual/direction-synthesis.js +124 -0
  69. package/src/agent_team/atoms/visual/dom-rect-export.js +129 -0
  70. package/src/agent_team/atoms/visual/image-diff.js +186 -0
  71. package/src/agent_team/atoms/visual/layout-competition.js +8 -0
  72. package/src/agent_team/atoms/visual/page-evidence-capture.js +806 -0
  73. package/src/agent_team/atoms/visual/playwright-runtime.js +8 -0
  74. package/src/agent_team/atoms/visual/review-aggregate.js +87 -0
  75. package/src/agent_team/atoms/visual/review.js +77 -0
  76. package/src/agent_team/atoms/visual/visual-report.js +197 -0
  77. package/src/agent_team/atoms/visual/visual-token-export.js +417 -0
  78. package/src/agent_team/common.js +311 -0
  79. package/src/core/atoms/agent-invoke.js +684 -0
  80. package/src/core/atoms/event/ingest.js +30 -0
  81. package/src/core/atoms/experience/digest.js +55 -0
  82. package/src/core/atoms/gate/artifact-verify.js +83 -0
  83. package/src/core/atoms/gate/competitor-contract-validate.js +119 -0
  84. package/src/core/atoms/gate/evidence-collect.js +64 -0
  85. package/src/core/atoms/gate/payload-from-phase-run.js +78 -0
  86. package/src/core/atoms/gate/visual-benchmark-validate.js +183 -0
  87. package/src/core/atoms/gate/workbench.js +79 -0
  88. package/src/core/atoms/intake/classify.js +15 -0
  89. package/src/core/atoms/intake/design-skill-select.js +16 -0
  90. package/src/core/atoms/intake/preview-or-refine.js +19 -0
  91. package/src/core/atoms/intake/recommend.js +20 -0
  92. package/src/core/atoms/intake/research-same-category.js +19 -0
  93. package/src/core/atoms/intervention/create.js +30 -0
  94. package/src/core/atoms/memory/manage.js +27 -0
  95. package/src/core/atoms/memory/mount.js +13 -0
  96. package/src/core/atoms/mission/create.js +19 -0
  97. package/src/core/atoms/mission/list.js +14 -0
  98. package/src/core/atoms/mission/show.js +26 -0
  99. package/src/core/atoms/notify/checkpoint.js +46 -0
  100. package/src/core/atoms/phase-run/complete.js +42 -0
  101. package/src/core/atoms/phase-run/fail.js +40 -0
  102. package/src/core/atoms/phase-run/persist.js +39 -0
  103. package/src/core/atoms/phase-run/start.js +30 -0
  104. package/src/core/atoms/plugin/run.js +76 -0
  105. package/src/core/atoms/policy/gate.js +90 -0
  106. package/src/core/atoms/policy/resolve.js +11 -0
  107. package/src/core/atoms/storage/sync.js +57 -0
  108. package/src/core/atoms/workflow/advance.js +25 -0
  109. package/src/core/atoms/workflow/guard.js +127 -0
  110. package/src/core/atoms/workflow/state-load.js +37 -0
  111. package/src/core/bindings.js +37 -0
  112. package/src/core/change-artifacts.js +134 -0
  113. package/src/core/competitive-score.js +265 -0
  114. package/src/core/concurrency-policy.js +35 -0
  115. package/src/core/execution-log.js +108 -0
  116. package/src/core/intake-recommendation.js +134 -0
  117. package/src/core/memory-state.js +62 -0
  118. package/src/core/mission-state.js +780 -0
  119. package/src/core/pending-gates.js +66 -0
  120. package/src/core/phase-cost.js +116 -0
  121. package/src/core/phase-run-read.js +27 -0
  122. package/src/core/policy-overlay.js +180 -0
  123. package/src/core/state-store.js +201 -0
  124. package/src/core/workflow-executor.js +998 -0
  125. package/src/core/workflow-integrity.js +111 -0
  126. package/src/core/workflow-loader.js +163 -0
  127. package/src/core/workflow-rerun.js +84 -0
  128. package/src/core/workflow-state.js +119 -0
  129. package/src/server.js +1307 -0
  130. package/workflows/corps.yaml +479 -0
  131. package/workflows/yolo.yaml +233 -0
  132. package/xflow/README.md +76 -0
  133. package/xflow/aha/SKILL.md +82 -0
  134. package/xflow/aha/scripts/append_aha.py +50 -0
  135. package/xflow/archive/scripts/archive_change.py +338 -0
  136. package/xflow/archive/templates/merge-design.md +54 -0
  137. package/xflow/archive/templates/merge-workflow.md +26 -0
  138. package/xflow/atoms/__init__.py +6 -0
  139. package/xflow/atoms/a1_issue_create_or_patch.py +70 -0
  140. package/xflow/atoms/a2_branch_create_and_link.py +42 -0
  141. package/xflow/atoms/a3_repo_doctor.py +11 -0
  142. package/xflow/atoms/a4_project_set_status.py +11 -0
  143. package/xflow/atoms/a5_archive_commit_push_close.py +11 -0
  144. package/xflow/atoms/a6_pr_create.py +72 -0
  145. package/xflow/atoms/b1_change_scaffold.py +11 -0
  146. package/xflow/atoms/b2_status_read.py +16 -0
  147. package/xflow/atoms/b2b_status_write.py +20 -0
  148. package/xflow/atoms/b3_status_transition.py +27 -0
  149. package/xflow/atoms/b4_handoff_scaffold_or_refresh.py +11 -0
  150. package/xflow/atoms/b5_aha_append.py +11 -0
  151. package/xflow/atoms/b6_takein_digest.py +11 -0
  152. package/xflow/atoms/b6b_lookback_digest.py +11 -0
  153. package/xflow/atoms/b7_change_mission_bind.py +43 -0
  154. package/xflow/atoms/c1_mem.py +54 -0
  155. package/xflow/atoms/e6_gate_local_precheck.py +23 -0
  156. package/xflow/atoms/h1_design_lite_gate.py +74 -0
  157. package/xflow/atoms/i6a_tdd_run.py +104 -0
  158. package/xflow/atoms/i6b_tdd_proof_validate.py +165 -0
  159. package/xflow/atoms/i6c_tdd_quality_review.py +357 -0
  160. package/xflow/atoms/j1_tests_run.py +129 -0
  161. package/xflow/atoms/j4a_spec_consistency_structural.py +105 -0
  162. package/xflow/atoms/j4b_spec_delta_review.py +316 -0
  163. package/xflow/atoms/j4c_openspec_migration_map.py +121 -0
  164. package/xflow/atoms/j4d_spec_clarify_gate.py +373 -0
  165. package/xflow/atoms/j6_cr_review.py +443 -0
  166. package/xflow/atoms/k1_artifacts_complete_check.py +39 -0
  167. package/xflow/atoms/k2_merge_snippets_apply.py +11 -0
  168. package/xflow/atoms/k3_handoff_refresh.py +14 -0
  169. package/xflow/atoms/k4_mem_lesson_persist.py +65 -0
  170. package/xflow/atoms/k5_archive_publish.py +21 -0
  171. package/xflow/atoms/k6_aha_merge.py +42 -0
  172. package/xflow/atoms/tdd_lib.py +747 -0
  173. package/xflow/corps/SKILL.md +109 -0
  174. package/xflow/exec/scripts/set_issue_in_progress.py +159 -0
  175. package/xflow/goal/SKILL.md +150 -0
  176. package/xflow/handoff/SKILL.md +95 -0
  177. package/xflow/handoff/scripts/scaffold_handoff.py +304 -0
  178. package/xflow/lookback/SKILL.md +66 -0
  179. package/xflow/lookback/scripts/lookback_digest.py +46 -0
  180. package/xflow/openissue/scripts/open_issue_flow.py +438 -0
  181. package/xflow/plan/SKILL.md +38 -0
  182. package/xflow/ralph/SKILL.md +123 -0
  183. package/xflow/scripts/check_installed_xflow_skill_sync.sh +44 -0
  184. package/xflow/scripts/common.py +480 -0
  185. package/xflow/scripts/doctor.py +278 -0
  186. package/xflow/scripts/sync_installed_xflow_skill.sh +134 -0
  187. package/xflow/takein/SKILL.md +81 -0
  188. package/xflow/takein/scripts/takein_digest.py +88 -0
  189. package/xflow/yolo/SKILL.md +66 -0
  190. package/xflow/yolo/scripts/scaffold_specs_change.py +673 -0
  191. package/xflow/yolo/scripts/yolo_gate.py +312 -0
  192. package/xflow/yolo/templates/DESIGN.md +179 -0
@@ -0,0 +1,79 @@
1
+ import { readJsonFile, resolveChangePath, writeJsonFile } from '../../change-artifacts.js';
2
+ import { resolveBoundMissionId } from '../../bindings.js';
3
+ import { getMissionDetail } from '../../mission-state.js';
4
+ import { readStore, resolveDataFile } from '../../state-store.js';
5
+
6
+ function summarizeGateDecisions(detail) {
7
+ const gates = Array.isArray(detail.gate_decisions) ? detail.gate_decisions : [];
8
+ return {
9
+ total: gates.length,
10
+ pass: gates.filter((gate) => gate.result === 'pass').length,
11
+ fail: gates.filter((gate) => gate.result === 'fail').length,
12
+ needs_human: gates.filter((gate) => gate.result === 'needs_human').length,
13
+ };
14
+ }
15
+
16
+ function deriveRecommendation(detail, qaAcceptance, evidencePacket) {
17
+ if (qaAcceptance?.qa_status === 'accepted' && (evidencePacket?.evidence_count || 0) > 0) {
18
+ return 'pass';
19
+ }
20
+ if (qaAcceptance?.qa_status === 'rejected') {
21
+ return 'fail';
22
+ }
23
+ if ((detail.gate_decisions || []).some((gate) => gate.result === 'fail')) {
24
+ return 'fail';
25
+ }
26
+ return 'needs_human';
27
+ }
28
+
29
+ export async function gateWorkbench(input = {}, context = {}) {
30
+ const projectRoot = context.projectRoot || process.cwd();
31
+ const changeId = context.changeId || input.change_id || null;
32
+ if (!changeId) {
33
+ throw new Error('change_id is required for E3.gate.workbench');
34
+ }
35
+
36
+ const dataFile = resolveDataFile({ projectRoot, explicitPath: input.data_file || null });
37
+ const missionId = resolveBoundMissionId({
38
+ projectRoot,
39
+ changeId,
40
+ missionId: input.mission_id || null,
41
+ runtimeMissionId: context.runtime?.missionId || null,
42
+ });
43
+
44
+ if (!missionId) {
45
+ throw new Error('mission_id is required for E3.gate.workbench');
46
+ }
47
+
48
+ const state = await readStore(dataFile);
49
+ const detail = getMissionDetail(state, missionId);
50
+ const qaAcceptance = readJsonFile(resolveChangePath(projectRoot, changeId, 'qa_acceptance.json'), null);
51
+ const evidencePacket = readJsonFile(resolveChangePath(projectRoot, changeId, 'evidence/gate_final.json'), null)
52
+ || readJsonFile(resolveChangePath(projectRoot, changeId, 'evidence/qa.json'), null);
53
+ const recommendation = deriveRecommendation(detail, qaAcceptance, evidencePacket);
54
+
55
+ const workbench = {
56
+ version: 1,
57
+ change_id: changeId,
58
+ mission_id: missionId,
59
+ generated_at: new Date().toISOString(),
60
+ mission_status: detail.mission.status,
61
+ execution_overview: detail.execution_overview,
62
+ gate_summary: summarizeGateDecisions(detail),
63
+ qa_acceptance: qaAcceptance,
64
+ evidence: evidencePacket,
65
+ recommendation,
66
+ review_queue: (detail.gate_decisions || []).filter((gate) => gate.result !== 'pass'),
67
+ recent_gates: detail.gate_decisions || [],
68
+ };
69
+
70
+ const outputPath = resolveChangePath(projectRoot, changeId, 'gate_final.json');
71
+ writeJsonFile(outputPath, workbench);
72
+
73
+ return {
74
+ ok: true,
75
+ verdict: recommendation,
76
+ gate_final_file: outputPath,
77
+ workbench,
78
+ };
79
+ }
@@ -0,0 +1,15 @@
1
+ import { resolveChangePath, writeJsonFile } from '../../change-artifacts.js';
2
+
3
+ export async function intakeClassify(input = {}, context = {}) {
4
+ const projectRoot = context.projectRoot || process.cwd();
5
+ const changeId = context.changeId || input.change_id || null;
6
+ if (!changeId) throw new Error('change_id is required for G2.intake.classify');
7
+ const classification = {
8
+ change_type: context.input?.change_type || input.change_type || 'backend',
9
+ risk_level: input.risk_level || 'medium',
10
+ ui_heavy: Boolean(input.ui_heavy || ['frontend', 'full-stack'].includes(context.input?.change_type || '')),
11
+ };
12
+ const outputPath = resolveChangePath(projectRoot, changeId, 'intake_classification.json');
13
+ writeJsonFile(outputPath, classification);
14
+ return { ok: true, output_file: outputPath, classification };
15
+ }
@@ -0,0 +1,16 @@
1
+ import { resolveChangePath, writeJsonFile } from '../../change-artifacts.js';
2
+
3
+ export async function designSkillSelect(input = {}, context = {}) {
4
+ const projectRoot = context.projectRoot || process.cwd();
5
+ const changeId = context.changeId || input.change_id || null;
6
+ if (!changeId) throw new Error('change_id is required for G4.design_skill.select');
7
+ const payload = {
8
+ version: 1,
9
+ change_id: changeId,
10
+ selected_skill: input.skill || 'design-contract-lite',
11
+ rationale: input.rationale || 'Selected the default heavy-track design skill for consistent output.',
12
+ };
13
+ const outputPath = resolveChangePath(projectRoot, changeId, 'design_skill_selection.json');
14
+ writeJsonFile(outputPath, payload);
15
+ return { ok: true, output_file: outputPath, payload };
16
+ }
@@ -0,0 +1,19 @@
1
+ import { resolveChangePath, writeJsonFile } from '../../change-artifacts.js';
2
+
3
+ export async function intakePreviewOrRefine(input = {}, context = {}) {
4
+ const projectRoot = context.projectRoot || process.cwd();
5
+ const changeId = context.changeId || input.change_id || null;
6
+ if (!changeId) throw new Error('change_id is required for G1.intake.preview_or_refine');
7
+ const mode = input.mode || 'preview';
8
+ const outputPath = resolveChangePath(projectRoot, changeId, `intake_${mode}.json`);
9
+ const payload = {
10
+ version: 1,
11
+ mode,
12
+ change_id: changeId,
13
+ summary: input.summary || context.input?.title || '',
14
+ answers: input.answers || {},
15
+ constraints: input.constraints || {},
16
+ };
17
+ writeJsonFile(outputPath, payload);
18
+ return { ok: true, output_file: outputPath, payload };
19
+ }
@@ -0,0 +1,20 @@
1
+ import { resolveEffectivePolicy } from '../../policy-overlay.js';
2
+ import { writeWorkflowRecommendation } from '../../intake-recommendation.js';
3
+
4
+ export async function intakeRecommend(input = {}, context = {}) {
5
+ const projectRoot = context.projectRoot || process.cwd();
6
+ const changeId = context.changeId || input.change_id;
7
+ if (!changeId) throw new Error('change_id is required for G5.intake.recommend');
8
+ const policyResult = resolveEffectivePolicy({
9
+ projectRoot,
10
+ changeId,
11
+ executionMode: input.execution_mode || null,
12
+ writeArtifact: true,
13
+ });
14
+ return writeWorkflowRecommendation({
15
+ projectRoot,
16
+ changeId,
17
+ input: { ...(context.input || {}), ...input },
18
+ effectivePolicy: policyResult.policy || policyResult.profile || {},
19
+ });
20
+ }
@@ -0,0 +1,19 @@
1
+ import { resolveChangePath, writeJsonFile } from '../../change-artifacts.js';
2
+
3
+ export async function researchSameCategory(input = {}, context = {}) {
4
+ const projectRoot = context.projectRoot || process.cwd();
5
+ const changeId = context.changeId || input.change_id || null;
6
+ if (!changeId) throw new Error('change_id is required for G3.research.same_category');
7
+ const payload = {
8
+ version: 1,
9
+ change_id: changeId,
10
+ references: [
11
+ { type: 'interaction', summary: 'Same-category interaction patterns reviewed.' },
12
+ { type: 'information_architecture', summary: 'Module boundaries compared against adjacent products.' },
13
+ { type: 'visual', summary: 'Visual references captured for later synthesis.' },
14
+ ],
15
+ };
16
+ const outputPath = resolveChangePath(projectRoot, changeId, 'same_category_research.json');
17
+ writeJsonFile(outputPath, payload);
18
+ return { ok: true, output_file: outputPath, payload };
19
+ }
@@ -0,0 +1,30 @@
1
+ import { createIntervention } from '../../mission-state.js';
2
+ import { mutateStore, resolveDataFile } from '../../state-store.js';
3
+ import { resolveBoundMissionId } from '../../bindings.js';
4
+
5
+ export async function interventionCreate(input = {}, context = {}) {
6
+ const projectRoot = context.projectRoot || process.cwd();
7
+ const dataFile = resolveDataFile({ projectRoot, explicitPath: input.data_file || null });
8
+ const missionId = resolveBoundMissionId({
9
+ projectRoot,
10
+ changeId: context.changeId || null,
11
+ missionId: input.mission_id || null,
12
+ runtimeMissionId: context.runtime?.missionId || null,
13
+ });
14
+
15
+ if (!missionId) {
16
+ throw new Error('mission_id is required for D5.intervention.create');
17
+ }
18
+
19
+ const result = await mutateStore(
20
+ (state) => createIntervention(state, missionId, input),
21
+ dataFile,
22
+ );
23
+
24
+ return {
25
+ ok: true,
26
+ data_file: dataFile,
27
+ mission_id: missionId,
28
+ ...result,
29
+ };
30
+ }
@@ -0,0 +1,27 @@
1
+ import { createMemoryItem, listMemoryItems, resolveMemoryMissionId } from '../../memory-state.js';
2
+ import { mutateStore, readStore, resolveDataFile } from '../../state-store.js';
3
+
4
+ export async function memoryItemManage(input = {}, context = {}) {
5
+ const projectRoot = context.projectRoot || process.cwd();
6
+ const missionId = resolveMemoryMissionId(input, context);
7
+ const dataFile = resolveDataFile({ projectRoot, explicitPath: input.data_file || null });
8
+ const action = input.action || 'create';
9
+
10
+ if (action === 'list') {
11
+ const state = await readStore(dataFile);
12
+ return {
13
+ ok: true,
14
+ items: listMemoryItems(state, { ...input, mission_id: missionId }),
15
+ };
16
+ }
17
+
18
+ const item = await mutateStore(
19
+ (state) => createMemoryItem(state, { ...input, mission_id: missionId }),
20
+ dataFile,
21
+ );
22
+
23
+ return {
24
+ ok: true,
25
+ item,
26
+ };
27
+ }
@@ -0,0 +1,13 @@
1
+ import { buildMemoryMount, resolveMemoryMissionId } from '../../memory-state.js';
2
+ import { readStore, resolveDataFile } from '../../state-store.js';
3
+
4
+ export async function memoryMount(input = {}, context = {}) {
5
+ const projectRoot = context.projectRoot || process.cwd();
6
+ const missionId = resolveMemoryMissionId(input, context);
7
+ const dataFile = resolveDataFile({ projectRoot, explicitPath: input.data_file || null });
8
+ const state = await readStore(dataFile);
9
+ return {
10
+ ok: true,
11
+ mount: buildMemoryMount(state, { ...input, mission_id: missionId }),
12
+ };
13
+ }
@@ -0,0 +1,19 @@
1
+ import { createMission } from '../../mission-state.js';
2
+ import { mutateStore, resolveDataFile } from '../../state-store.js';
3
+
4
+ export async function missionCreate(input = {}, context = {}) {
5
+ const projectRoot = context.projectRoot || process.cwd();
6
+ const dataFile = resolveDataFile({ projectRoot, explicitPath: input.data_file || null });
7
+ const result = await mutateStore(
8
+ (state) => createMission(state, { ...input, change_id: context.changeId || null }),
9
+ dataFile,
10
+ );
11
+
12
+ return {
13
+ ok: true,
14
+ data_file: dataFile,
15
+ mission_id: result.mission.mission_id,
16
+ mission: result.mission,
17
+ replay_event: result.replay_event,
18
+ };
19
+ }
@@ -0,0 +1,14 @@
1
+ import { listMissions } from '../../mission-state.js';
2
+ import { readStore, resolveDataFile } from '../../state-store.js';
3
+
4
+ export async function missionList(input = {}, context = {}) {
5
+ const projectRoot = context.projectRoot || process.cwd();
6
+ const dataFile = resolveDataFile({ projectRoot, explicitPath: input.data_file || null });
7
+ const state = await readStore(dataFile);
8
+
9
+ return {
10
+ ok: true,
11
+ data_file: dataFile,
12
+ missions: listMissions(state),
13
+ };
14
+ }
@@ -0,0 +1,26 @@
1
+ import { getMissionDetail } from '../../mission-state.js';
2
+ import { resolveBoundMissionId } from '../../bindings.js';
3
+ import { readStore, resolveDataFile } from '../../state-store.js';
4
+
5
+ export async function missionShow(input = {}, context = {}) {
6
+ const projectRoot = context.projectRoot || process.cwd();
7
+ const dataFile = resolveDataFile({ projectRoot, explicitPath: input.data_file || null });
8
+ const missionId = resolveBoundMissionId({
9
+ projectRoot,
10
+ changeId: context.changeId || null,
11
+ missionId: input.mission_id || null,
12
+ runtimeMissionId: context.runtime?.missionId || null,
13
+ });
14
+
15
+ if (!missionId) {
16
+ throw new Error('mission_id is required for D2b.mission.show');
17
+ }
18
+
19
+ const state = await readStore(dataFile);
20
+ return {
21
+ ok: true,
22
+ data_file: dataFile,
23
+ mission_id: missionId,
24
+ detail: getMissionDetail(state, missionId),
25
+ };
26
+ }
@@ -0,0 +1,46 @@
1
+ import { resolveChangePath, writeJsonFile } from '../../change-artifacts.js';
2
+
3
+ export async function notifyCheckpoint(input = {}, context = {}) {
4
+ const projectRoot = context.projectRoot || process.cwd();
5
+ const changeId = context.changeId || input.change_id;
6
+ if (!changeId) throw new Error('change_id is required for notify.checkpoint');
7
+
8
+ const phase = input.phase || context.phase?.id || 'checkpoint';
9
+ const provider = input.provider || 'file';
10
+ if (!['file', 'mock'].includes(provider)) {
11
+ return {
12
+ ok: false,
13
+ error: `Unsupported notify provider: ${provider}`,
14
+ supported_providers: ['file', 'mock'],
15
+ };
16
+ }
17
+
18
+ const evidence = {
19
+ version: 1,
20
+ provider,
21
+ phase,
22
+ change_id: changeId,
23
+ mission_id: input.mission_id || context.runtime?.missionId || null,
24
+ recipient: input.recipient || null,
25
+ artifact_url: input.artifact_url || input.artifact || null,
26
+ message: input.message || `Checkpoint notification for ${phase}`,
27
+ sent_at: new Date().toISOString(),
28
+ status: provider === 'mock' ? 'mock_sent' : 'sent',
29
+ approves_gate: false,
30
+ authority: 'notification_only_human_gate_ack_required',
31
+ };
32
+ const outputPath = resolveChangePath(projectRoot, changeId, `notification/checkpoint-${sanitizeFilePart(phase)}.json`);
33
+ writeJsonFile(outputPath, evidence);
34
+ return {
35
+ ok: true,
36
+ provider,
37
+ phase,
38
+ notification_file: outputPath,
39
+ approves_gate: false,
40
+ evidence,
41
+ };
42
+ }
43
+
44
+ function sanitizeFilePart(value) {
45
+ return String(value || 'checkpoint').replace(/[^a-zA-Z0-9._-]+/g, '-');
46
+ }
@@ -0,0 +1,42 @@
1
+ import { resolveBoundMissionId } from '../../bindings.js';
2
+ import { completePhaseRunSession, findPhaseRunSession } from '../../mission-state.js';
3
+ import { mutateStore, resolveDataFile } from '../../state-store.js';
4
+
5
+ export async function phaseRunComplete(input = {}, context = {}) {
6
+ const projectRoot = context.projectRoot || process.cwd();
7
+ const dataFile = resolveDataFile({ projectRoot, explicitPath: input.data_file || null });
8
+ const missionId = resolveBoundMissionId({
9
+ projectRoot,
10
+ changeId: context.changeId || null,
11
+ missionId: input.mission_id || null,
12
+ runtimeMissionId: context.runtime?.missionId || null,
13
+ });
14
+
15
+ if (!missionId) {
16
+ throw new Error('mission_id is required for D3c.phase_run.complete');
17
+ }
18
+
19
+ const sessionId = input.session_id || context.runtime?.phaseRunSessionId || null;
20
+
21
+ const result = await mutateStore((state) => {
22
+ const session = sessionId ? findPhaseRunSession(state, missionId, sessionId) : findPhaseRunSession(state, missionId);
23
+ if (!session) {
24
+ throw new Error(`No phase run session found for mission ${missionId}`);
25
+ }
26
+
27
+ const completed = completePhaseRunSession(state, missionId, session.session_id, {
28
+ phase_run_id: input.phase_run_id || context.runtime?.phaseRunId || session.phase_run_id || null,
29
+ status: input.status || 'completed',
30
+ stop_reason: input.stop_reason || null,
31
+ completed_at: input.completed_at || null,
32
+ });
33
+ return { session: completed };
34
+ }, dataFile);
35
+
36
+ return {
37
+ ok: true,
38
+ data_file: dataFile,
39
+ mission_id: missionId,
40
+ session: result.session,
41
+ };
42
+ }
@@ -0,0 +1,40 @@
1
+ import { resolveBoundMissionId } from '../../bindings.js';
2
+ import { failPhaseRunSession, findPhaseRunSession } from '../../mission-state.js';
3
+ import { mutateStore, resolveDataFile } from '../../state-store.js';
4
+
5
+ export async function phaseRunFail(input = {}, context = {}) {
6
+ const projectRoot = context.projectRoot || process.cwd();
7
+ const dataFile = resolveDataFile({ projectRoot, explicitPath: input.data_file || null });
8
+ const missionId = resolveBoundMissionId({
9
+ projectRoot,
10
+ changeId: context.changeId || null,
11
+ missionId: input.mission_id || null,
12
+ runtimeMissionId: context.runtime?.missionId || null,
13
+ });
14
+
15
+ if (!missionId) {
16
+ throw new Error('mission_id is required for D3d.phase_run.fail');
17
+ }
18
+
19
+ const sessionId = input.session_id || context.runtime?.phaseRunSessionId || null;
20
+
21
+ const result = await mutateStore((state) => {
22
+ const session = sessionId ? findPhaseRunSession(state, missionId, sessionId) : findPhaseRunSession(state, missionId);
23
+ if (!session) {
24
+ throw new Error(`No phase run session found for mission ${missionId}`);
25
+ }
26
+
27
+ const failed = failPhaseRunSession(state, missionId, session.session_id, {
28
+ summary: input.summary || input.stop_reason || 'phase_run_failed',
29
+ detail: input.detail || null,
30
+ });
31
+ return { session: failed };
32
+ }, dataFile);
33
+
34
+ return {
35
+ ok: true,
36
+ data_file: dataFile,
37
+ mission_id: missionId,
38
+ session: result.session,
39
+ };
40
+ }
@@ -0,0 +1,39 @@
1
+ import { resolveBoundMissionId } from '../../bindings.js';
2
+ import { findPhaseRunSession, persistPhaseRun } from '../../mission-state.js';
3
+ import { mutateStore, resolveDataFile } from '../../state-store.js';
4
+
5
+ export async function phaseRunPersist(input = {}, context = {}) {
6
+ const projectRoot = context.projectRoot || process.cwd();
7
+ const dataFile = resolveDataFile({ projectRoot, explicitPath: input.data_file || null });
8
+ const missionId = resolveBoundMissionId({
9
+ projectRoot,
10
+ changeId: context.changeId || null,
11
+ missionId: input.mission_id || null,
12
+ runtimeMissionId: context.runtime?.missionId || null,
13
+ });
14
+
15
+ if (!missionId) {
16
+ throw new Error('mission_id is required for D3b.phase_run.persist');
17
+ }
18
+
19
+ const payload = {
20
+ ...input,
21
+ session_id: input.session_id || context.runtime?.phaseRunSessionId || null,
22
+ phase_run_id: input.phase_run_id || context.runtime?.phaseRunId || null,
23
+ };
24
+
25
+ const result = await mutateStore((state) => {
26
+ const phaseRun = persistPhaseRun(state, missionId, payload);
27
+ const session = findPhaseRunSession(state, missionId, payload.session_id || null);
28
+ return { phaseRun, session };
29
+ }, dataFile);
30
+
31
+ return {
32
+ ok: true,
33
+ data_file: dataFile,
34
+ mission_id: missionId,
35
+ phase_run_id: result.phaseRun.phase_run_id,
36
+ phase_run: result.phaseRun,
37
+ session: result.session,
38
+ };
39
+ }
@@ -0,0 +1,30 @@
1
+ import { resolveBoundMissionId } from '../../bindings.js';
2
+ import { startPhaseRunSession } from '../../mission-state.js';
3
+ import { mutateStore, resolveDataFile } from '../../state-store.js';
4
+
5
+ export async function phaseRunStart(input = {}, context = {}) {
6
+ const projectRoot = context.projectRoot || process.cwd();
7
+ const dataFile = resolveDataFile({ projectRoot, explicitPath: input.data_file || null });
8
+ const missionId = resolveBoundMissionId({
9
+ projectRoot,
10
+ changeId: context.changeId || null,
11
+ missionId: input.mission_id || null,
12
+ runtimeMissionId: context.runtime?.missionId || null,
13
+ });
14
+
15
+ if (!missionId) {
16
+ throw new Error('mission_id is required for D3a.phase_run.start');
17
+ }
18
+
19
+ const session = await mutateStore(
20
+ (state) => startPhaseRunSession(state, missionId, input),
21
+ dataFile,
22
+ );
23
+
24
+ return {
25
+ ok: true,
26
+ data_file: dataFile,
27
+ mission_id: missionId,
28
+ session,
29
+ };
30
+ }
@@ -0,0 +1,76 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { resolve } from 'node:path';
3
+
4
+ import { resolveChangePath, writeJsonFile } from '../../change-artifacts.js';
5
+
6
+ export async function pluginRun(input = {}, context = {}) {
7
+ const projectRoot = context.projectRoot || process.cwd();
8
+ const changeId = context.changeId || input.change_id;
9
+ if (!changeId) throw new Error('change_id is required for plugin.run');
10
+
11
+ const type = input.type || 'script';
12
+ const ref = input.ref || '';
13
+ const dryRun = input.dry_run !== false;
14
+ const payload = input.payload || {};
15
+ const result = dryRun
16
+ ? dryRunResult(type, ref, payload)
17
+ : executePlugin({ type, ref, payload, projectRoot, timeoutMs: Number(input.timeout_ms || 60000) });
18
+
19
+ const output = {
20
+ version: 1,
21
+ change_id: changeId,
22
+ type,
23
+ ref,
24
+ dry_run: dryRun,
25
+ generated_at: new Date().toISOString(),
26
+ result,
27
+ };
28
+ const outputPath = resolveChangePath(projectRoot, changeId, `plugin/${sanitizeFilePart(type)}-${sanitizeFilePart(ref || 'plugin')}.json`);
29
+ writeJsonFile(outputPath, output);
30
+ return {
31
+ ok: result.status !== 'error',
32
+ plugin_result_file: outputPath,
33
+ result,
34
+ };
35
+ }
36
+
37
+ function dryRunResult(type, ref, payload) {
38
+ if (!['script', 'webhook', 'agent', 'skill', 'interactive'].includes(type)) {
39
+ return { status: 'error', error: `Unknown plugin type: ${type}` };
40
+ }
41
+ return {
42
+ status: 'dry_run',
43
+ type,
44
+ ref,
45
+ payload_preview: payload,
46
+ message: 'Plugin execution was recorded without external side effects.',
47
+ };
48
+ }
49
+
50
+ function executePlugin({ type, ref, payload, projectRoot, timeoutMs }) {
51
+ if (type !== 'script') {
52
+ return { status: 'error', error: `Non-dry-run ${type} plugins are not supported by the generic v1 provider` };
53
+ }
54
+ const scriptPath = resolve(projectRoot, ref);
55
+ const started = Date.now();
56
+ const result = spawnSync('bash', [scriptPath], {
57
+ cwd: projectRoot,
58
+ encoding: 'utf8',
59
+ timeout: timeoutMs,
60
+ env: {
61
+ ...process.env,
62
+ PAYLOAD: JSON.stringify(payload),
63
+ },
64
+ });
65
+ return {
66
+ status: result.status === 0 ? 'ok' : 'error',
67
+ exit_code: result.status,
68
+ duration_ms: Date.now() - started,
69
+ stdout: result.stdout || '',
70
+ stderr: result.stderr || '',
71
+ };
72
+ }
73
+
74
+ function sanitizeFilePart(value) {
75
+ return String(value || 'plugin').replace(/[^a-zA-Z0-9._-]+/g, '-').slice(0, 80);
76
+ }