@urielsh/prodify 0.1.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.
Files changed (273) hide show
  1. package/.prodify/AGENTS.md +56 -0
  2. package/.prodify/README.md +10 -0
  3. package/.prodify/artifacts/01-understand.md +28 -0
  4. package/.prodify/artifacts/02-diagnose.md +26 -0
  5. package/.prodify/artifacts/03-architecture.md +30 -0
  6. package/.prodify/artifacts/04-plan.md +35 -0
  7. package/.prodify/artifacts/05-refactor.md +24 -0
  8. package/.prodify/artifacts/06-validate.md +17 -0
  9. package/.prodify/artifacts/README.md +6 -0
  10. package/.prodify/artifacts/architecture_spec.md +29 -0
  11. package/.prodify/artifacts/artifact-validation-design.md +276 -0
  12. package/.prodify/artifacts/cli-command-design.md +299 -0
  13. package/.prodify/artifacts/completed-steps-tracking.md +201 -0
  14. package/.prodify/artifacts/diagnostic_report.md +45 -0
  15. package/.prodify/artifacts/hardening-patch-summary.md +57 -0
  16. package/.prodify/artifacts/hardening-verification-report.md +48 -0
  17. package/.prodify/artifacts/implementation_summary.md +19 -0
  18. package/.prodify/artifacts/improvement-evaluation-spec.md +148 -0
  19. package/.prodify/artifacts/next-step-resolver-design.md +570 -0
  20. package/.prodify/artifacts/orientation_map.md +32 -0
  21. package/.prodify/artifacts/persona-removal-audit.md +106 -0
  22. package/.prodify/artifacts/planning-alignment-report.md +189 -0
  23. package/.prodify/artifacts/refactor-plan-template-hardening.md +83 -0
  24. package/.prodify/artifacts/refactor-validate-loop-design.md +231 -0
  25. package/.prodify/artifacts/refactor_plan.md +21 -0
  26. package/.prodify/artifacts/refactor_plan.strict-example.md +31 -0
  27. package/.prodify/artifacts/run-state-design.md +292 -0
  28. package/.prodify/artifacts/run-summary-spec.md +149 -0
  29. package/.prodify/artifacts/run_state.json +14 -0
  30. package/.prodify/artifacts/sample-repo-test-plan.md +129 -0
  31. package/.prodify/artifacts/status-output-spec.md +349 -0
  32. package/.prodify/artifacts/step-selection-design.md +251 -0
  33. package/.prodify/artifacts/task-dispatcher-design.md +266 -0
  34. package/.prodify/artifacts/task-header-audit.md +42 -0
  35. package/.prodify/artifacts/task-log-enhancement-spec.md +264 -0
  36. package/.prodify/artifacts/task-protocol-hardening-plan.md +68 -0
  37. package/.prodify/artifacts/task-self-validation-spec.md +171 -0
  38. package/.prodify/artifacts/task_log.json +160 -0
  39. package/.prodify/artifacts/template-usage-audit.md +20 -0
  40. package/.prodify/artifacts/validation_report.md +22 -0
  41. package/.prodify/contracts/README.md +3 -0
  42. package/.prodify/contracts/architecture.contract.json +42 -0
  43. package/.prodify/contracts/diagnose.contract.json +42 -0
  44. package/.prodify/contracts/plan.contract.json +42 -0
  45. package/.prodify/contracts/refactor.contract.json +45 -0
  46. package/.prodify/contracts/understand.contract.json +42 -0
  47. package/.prodify/contracts/validate.contract.json +52 -0
  48. package/.prodify/contracts-src/README.md +4 -0
  49. package/.prodify/contracts-src/architecture.contract.md +29 -0
  50. package/.prodify/contracts-src/diagnose.contract.md +29 -0
  51. package/.prodify/contracts-src/plan.contract.md +29 -0
  52. package/.prodify/contracts-src/refactor.contract.md +32 -0
  53. package/.prodify/contracts-src/understand.contract.md +29 -0
  54. package/.prodify/contracts-src/validate.contract.md +35 -0
  55. package/.prodify/metrics/README.md +4 -0
  56. package/.prodify/planning.md +13 -0
  57. package/.prodify/project.md +15 -0
  58. package/.prodify/rules/01-global-operating-rules.md +21 -0
  59. package/.prodify/rules/02-analysis-discipline.md +17 -0
  60. package/.prodify/rules/03-diagnosis-severity-rules.md +42 -0
  61. package/.prodify/rules/04-architecture-rules.md +27 -0
  62. package/.prodify/rules/05-planning-rules.md +23 -0
  63. package/.prodify/rules/06-refactor-rules.md +20 -0
  64. package/.prodify/rules/07-validation-rules.md +25 -0
  65. package/.prodify/rules/08-output-format-rules.md +20 -0
  66. package/.prodify/rules/09-template-usage-rules.md +11 -0
  67. package/.prodify/rules/10-artifact-flow-and-orchestration-rules.md +40 -0
  68. package/.prodify/rules/README.md +3 -0
  69. package/.prodify/rules/example-rule.md +4 -0
  70. package/.prodify/runtime-commands.md +57 -0
  71. package/.prodify/skills/README.md +8 -0
  72. package/.prodify/skills/domain/react-frontend.skill.json +34 -0
  73. package/.prodify/skills/domain/typescript-backend.skill.json +34 -0
  74. package/.prodify/skills/quality-policy/maintainability-review.skill.json +25 -0
  75. package/.prodify/skills/quality-policy/security-hardening.skill.json +32 -0
  76. package/.prodify/skills/quality-policy/test-hardening.skill.json +23 -0
  77. package/.prodify/skills/registry.json +16 -0
  78. package/.prodify/skills/stage-method/architecture-method.skill.json +22 -0
  79. package/.prodify/skills/stage-method/codebase-scanning.skill.json +22 -0
  80. package/.prodify/skills/stage-method/diagnosis-method.skill.json +22 -0
  81. package/.prodify/skills/stage-method/planning-method.skill.json +22 -0
  82. package/.prodify/skills/stage-method/refactoring-method.skill.json +22 -0
  83. package/.prodify/skills/stage-method/validation-method.skill.json +22 -0
  84. package/.prodify/state.json +30 -0
  85. package/.prodify/tasks/01-understand.md +83 -0
  86. package/.prodify/tasks/02-diagnose.md +67 -0
  87. package/.prodify/tasks/03-architecture.md +70 -0
  88. package/.prodify/tasks/04-plan.md +71 -0
  89. package/.prodify/tasks/05-refactor.md +61 -0
  90. package/.prodify/tasks/06-validate.md +69 -0
  91. package/.prodify/tasks/README.md +3 -0
  92. package/.prodify/tasks/example-task.md +13 -0
  93. package/.prodify/templates/01-understand.template.md +18 -0
  94. package/.prodify/templates/02-diagnose.template.md +18 -0
  95. package/.prodify/templates/03-architecture.template.md +18 -0
  96. package/.prodify/templates/04-plan.template.md +22 -0
  97. package/.prodify/templates/05-refactor.template.md +19 -0
  98. package/.prodify/templates/06-validate.template.md +16 -0
  99. package/.prodify/templates/README.md +3 -0
  100. package/.prodify/templates/architecture_spec.template.md +29 -0
  101. package/.prodify/templates/diagnostic_report.template.md +45 -0
  102. package/.prodify/templates/example.template.md +5 -0
  103. package/.prodify/templates/implementation_summary.template.md +19 -0
  104. package/.prodify/templates/orientation_map.template.md +32 -0
  105. package/.prodify/templates/refactor_plan.template.md +24 -0
  106. package/.prodify/templates/validation_report.template.md +22 -0
  107. package/.prodify/version.json +5 -0
  108. package/AGENTS.md +305 -0
  109. package/LICENSE +201 -0
  110. package/README.md +118 -0
  111. package/assets/presets/default/canonical/AGENTS.md +54 -0
  112. package/assets/presets/default/canonical/artifacts/README.md +6 -0
  113. package/assets/presets/default/canonical/contracts-src/README.md +4 -0
  114. package/assets/presets/default/canonical/contracts-src/architecture.contract.md +56 -0
  115. package/assets/presets/default/canonical/contracts-src/diagnose.contract.md +49 -0
  116. package/assets/presets/default/canonical/contracts-src/plan.contract.md +42 -0
  117. package/assets/presets/default/canonical/contracts-src/refactor.contract.md +54 -0
  118. package/assets/presets/default/canonical/contracts-src/understand.contract.md +56 -0
  119. package/assets/presets/default/canonical/contracts-src/validate.contract.md +64 -0
  120. package/assets/presets/default/canonical/metrics/README.md +4 -0
  121. package/assets/presets/default/canonical/planning.md +13 -0
  122. package/assets/presets/default/canonical/project.md +15 -0
  123. package/assets/presets/default/canonical/rules/README.md +3 -0
  124. package/assets/presets/default/canonical/rules/example-rule.md +4 -0
  125. package/assets/presets/default/canonical/runtime-commands.md +57 -0
  126. package/assets/presets/default/canonical/skills/README.md +8 -0
  127. package/assets/presets/default/canonical/skills/domain/react-frontend.skill.json +34 -0
  128. package/assets/presets/default/canonical/skills/domain/typescript-backend.skill.json +34 -0
  129. package/assets/presets/default/canonical/skills/quality-policy/maintainability-review.skill.json +25 -0
  130. package/assets/presets/default/canonical/skills/quality-policy/security-hardening.skill.json +32 -0
  131. package/assets/presets/default/canonical/skills/quality-policy/test-hardening.skill.json +23 -0
  132. package/assets/presets/default/canonical/skills/registry.json +16 -0
  133. package/assets/presets/default/canonical/skills/stage-method/architecture-method.skill.json +22 -0
  134. package/assets/presets/default/canonical/skills/stage-method/codebase-scanning.skill.json +22 -0
  135. package/assets/presets/default/canonical/skills/stage-method/diagnosis-method.skill.json +22 -0
  136. package/assets/presets/default/canonical/skills/stage-method/planning-method.skill.json +22 -0
  137. package/assets/presets/default/canonical/skills/stage-method/refactoring-method.skill.json +22 -0
  138. package/assets/presets/default/canonical/skills/stage-method/validation-method.skill.json +22 -0
  139. package/assets/presets/default/canonical/state.json +30 -0
  140. package/assets/presets/default/canonical/tasks/README.md +3 -0
  141. package/assets/presets/default/canonical/tasks/example-task.md +13 -0
  142. package/assets/presets/default/canonical/templates/README.md +3 -0
  143. package/assets/presets/default/canonical/templates/example.template.md +5 -0
  144. package/assets/presets/default/preset.json +5 -0
  145. package/dist/cli.js +53 -0
  146. package/dist/commands/doctor.js +16 -0
  147. package/dist/commands/init.js +30 -0
  148. package/dist/commands/install.js +27 -0
  149. package/dist/commands/setup-agent.js +23 -0
  150. package/dist/commands/status.js +28 -0
  151. package/dist/commands/sync.js +29 -0
  152. package/dist/commands/update.js +18 -0
  153. package/dist/contracts/compiled-schema.js +37 -0
  154. package/dist/contracts/compiler.js +83 -0
  155. package/dist/contracts/freshness.js +52 -0
  156. package/dist/contracts/index.js +5 -0
  157. package/dist/contracts/parser.js +201 -0
  158. package/dist/contracts/schema.js +138 -0
  159. package/dist/contracts/source-schema.js +111 -0
  160. package/dist/core/agent-runtime.js +36 -0
  161. package/dist/core/agent-setup.js +111 -0
  162. package/dist/core/doctor.js +155 -0
  163. package/dist/core/errors.js +19 -0
  164. package/dist/core/flow-state.js +262 -0
  165. package/dist/core/fs.js +50 -0
  166. package/dist/core/install.js +44 -0
  167. package/dist/core/managed-files.js +106 -0
  168. package/dist/core/paths.js +56 -0
  169. package/dist/core/preset-validation.js +43 -0
  170. package/dist/core/prompt-builder.js +63 -0
  171. package/dist/core/repo-context.js +77 -0
  172. package/dist/core/repo-root.js +55 -0
  173. package/dist/core/skill-resolution.js +123 -0
  174. package/dist/core/state.js +220 -0
  175. package/dist/core/status.js +293 -0
  176. package/dist/core/sync.js +63 -0
  177. package/dist/core/targets.js +48 -0
  178. package/dist/core/upgrade.js +57 -0
  179. package/dist/core/validation.js +138 -0
  180. package/dist/core/version-checks.js +35 -0
  181. package/dist/generators/claude.js +14 -0
  182. package/dist/generators/codex.js +14 -0
  183. package/dist/generators/copilot.js +29 -0
  184. package/dist/generators/header.js +13 -0
  185. package/dist/generators/opencode.js +14 -0
  186. package/dist/generators/shared.js +37 -0
  187. package/dist/index.js +9 -0
  188. package/dist/legacy/targets.js +55 -0
  189. package/dist/presets/default.js +3 -0
  190. package/dist/presets/loader.js +34 -0
  191. package/dist/presets/version.js +18 -0
  192. package/dist/scoring/model.js +262 -0
  193. package/dist/skills/loader.js +40 -0
  194. package/dist/skills/schema.js +159 -0
  195. package/dist/types.js +1 -0
  196. package/docs/canonical-prodify-layout.md +87 -0
  197. package/docs/claude-support.md +22 -0
  198. package/docs/cli-doctor-spec.md +52 -0
  199. package/docs/cli-init-spec.md +70 -0
  200. package/docs/cli-install-agent-spec.md +48 -0
  201. package/docs/cli-sync-spec.md +40 -0
  202. package/docs/codex-support.md +24 -0
  203. package/docs/compatibility-targets.md +59 -0
  204. package/docs/copilot-support.md +30 -0
  205. package/docs/default-preset.md +47 -0
  206. package/docs/generated-file-headers.md +45 -0
  207. package/docs/generation-rules.md +94 -0
  208. package/docs/idempotency-guarantees.md +31 -0
  209. package/docs/managed-file-detection.md +45 -0
  210. package/docs/manual-edit-conflicts.md +37 -0
  211. package/docs/opencode-support.md +27 -0
  212. package/docs/ownership-rules.md +39 -0
  213. package/docs/path-resolution-rules.md +40 -0
  214. package/docs/preset-structure.md +49 -0
  215. package/docs/skill-system.md +67 -0
  216. package/docs/versioning-and-upgrade-strategy.md +40 -0
  217. package/package.json +22 -0
  218. package/src/README.md +11 -0
  219. package/src/cli.ts +61 -0
  220. package/src/commands/doctor.ts +20 -0
  221. package/src/commands/init.ts +37 -0
  222. package/src/commands/setup-agent.ts +28 -0
  223. package/src/commands/status.ts +34 -0
  224. package/src/commands/update.ts +23 -0
  225. package/src/contracts/README.md +10 -0
  226. package/src/contracts/compiled-schema.ts +42 -0
  227. package/src/contracts/compiler.ts +111 -0
  228. package/src/contracts/freshness.ts +58 -0
  229. package/src/contracts/index.ts +11 -0
  230. package/src/contracts/parser.ts +253 -0
  231. package/src/contracts/source-schema.ts +141 -0
  232. package/src/core/agent-runtime.ts +53 -0
  233. package/src/core/agent-setup.ts +147 -0
  234. package/src/core/doctor.ts +171 -0
  235. package/src/core/errors.ts +28 -0
  236. package/src/core/flow-state.ts +333 -0
  237. package/src/core/fs.ts +59 -0
  238. package/src/core/paths.ts +63 -0
  239. package/src/core/preset-validation.ts +47 -0
  240. package/src/core/prompt-builder.ts +73 -0
  241. package/src/core/repo-context.ts +93 -0
  242. package/src/core/repo-root.ts +74 -0
  243. package/src/core/skill-resolution.ts +151 -0
  244. package/src/core/state.ts +264 -0
  245. package/src/core/status.ts +372 -0
  246. package/src/core/targets.ts +53 -0
  247. package/src/core/upgrade.ts +66 -0
  248. package/src/core/validation.ts +233 -0
  249. package/src/core/version-checks.ts +40 -0
  250. package/src/index.ts +13 -0
  251. package/src/presets/default.ts +7 -0
  252. package/src/presets/loader.ts +46 -0
  253. package/src/presets/version.ts +31 -0
  254. package/src/scoring/model.ts +332 -0
  255. package/src/skills/loader.ts +58 -0
  256. package/src/skills/schema.ts +197 -0
  257. package/src/types.ts +329 -0
  258. package/tests/integration/cli-flows.test.js +347 -0
  259. package/tests/unit/agent-setup.test.js +81 -0
  260. package/tests/unit/cli.test.js +28 -0
  261. package/tests/unit/contracts.test.js +61 -0
  262. package/tests/unit/helpers.js +28 -0
  263. package/tests/unit/paths.test.js +52 -0
  264. package/tests/unit/preset-loader.test.js +37 -0
  265. package/tests/unit/scoring.test.js +65 -0
  266. package/tests/unit/self-hosted-workspace.test.js +22 -0
  267. package/tests/unit/skills.test.js +115 -0
  268. package/tests/unit/state-and-flow.test.js +120 -0
  269. package/tests/unit/targets.test.js +13 -0
  270. package/tests/unit/validation.test.js +73 -0
  271. package/tests/unit/version.test.js +24 -0
  272. package/tsconfig.json +23 -0
  273. package/urielsh-prodify-0.1.0.tgz +0 -0
@@ -0,0 +1,293 @@
1
+ import fs from 'node:fs/promises';
2
+ import { loadDefaultPreset } from '../presets/loader.js';
3
+ import { inspectCompiledContracts } from '../contracts/freshness.js';
4
+ import { listConfiguredAgents, readGlobalAgentSetupState } from './agent-setup.js';
5
+ import { detectRuntimeAgentFromEnv } from './agent-runtime.js';
6
+ import { pathExists } from './fs.js';
7
+ import { getResumeDecision } from './flow-state.js';
8
+ import { resolveStageSkills } from './skill-resolution.js';
9
+ import { resolveCanonicalPath, resolveRepoPath, REQUIRED_CANONICAL_PATHS } from './paths.js';
10
+ import { readRuntimeState, RUNTIME_STATUS } from './state.js';
11
+ import { inspectVersionStatus } from './version-checks.js';
12
+ import { buildBootstrapPrompt, hasManualBootstrapGuidance } from './prompt-builder.js';
13
+ import { getRuntimeProfile } from './targets.js';
14
+ function describeCanonicalHealth(missingPaths) {
15
+ if (missingPaths.length === 0) {
16
+ return 'healthy';
17
+ }
18
+ return `missing ${missingPaths.join(', ')}`;
19
+ }
20
+ function hasStageValidationFailure(report) {
21
+ return report.runtimeState?.runtime.current_state === 'failed'
22
+ || report.runtimeState?.runtime.last_validation_result === 'fail';
23
+ }
24
+ function describeWorkspaceHealth(report) {
25
+ if (!report.initialized) {
26
+ return 'not initialized';
27
+ }
28
+ const issues = [];
29
+ if (!report.canonicalOk) {
30
+ issues.push(`canonical files: ${describeCanonicalHealth(report.canonicalMissing)}`);
31
+ }
32
+ if (report.versionStatus.status !== 'current') {
33
+ issues.push(`version/schema: ${report.versionStatus.status}`);
34
+ }
35
+ if (report.runtimeStateError) {
36
+ issues.push('runtime state unreadable');
37
+ }
38
+ if (!report.manualBootstrapReady) {
39
+ issues.push('bootstrap guidance incomplete');
40
+ }
41
+ return issues.length === 0 ? 'healthy' : `repair required (${issues.join('; ')})`;
42
+ }
43
+ function describeContractFreshness(report) {
44
+ if (!report.contractInventory) {
45
+ return 'unavailable';
46
+ }
47
+ if (report.contractInventory.ok) {
48
+ return `${report.contractInventory.compiledCount} compiled, synchronized`;
49
+ }
50
+ const parts = [];
51
+ if (report.contractInventory.missingCompiledStages.length > 0) {
52
+ parts.push(`missing compiled: ${report.contractInventory.missingCompiledStages.join(', ')}`);
53
+ }
54
+ if (report.contractInventory.staleStages.length > 0) {
55
+ parts.push(`stale: ${report.contractInventory.staleStages.join(', ')}`);
56
+ }
57
+ if (report.contractInventory.invalidStages.length > 0) {
58
+ parts.push(`invalid: ${report.contractInventory.invalidStages.join(', ')}`);
59
+ }
60
+ return parts.join('; ') || 'invalid';
61
+ }
62
+ function describeGlobalAgentSetup(report) {
63
+ return report.configuredAgents.length === 0 ? 'none configured' : report.configuredAgents.join(', ');
64
+ }
65
+ function describeSkillStage(report) {
66
+ return report.stageSkillResolution?.stage ?? 'unavailable';
67
+ }
68
+ function describeConsideredSkills(report) {
69
+ if (!report.stageSkillResolution) {
70
+ return 'unavailable';
71
+ }
72
+ if (report.stageSkillResolution.considered_skills.length === 0) {
73
+ return 'none';
74
+ }
75
+ return report.stageSkillResolution.considered_skills
76
+ .map((skill) => `${skill.id} [${skill.reason}]`)
77
+ .join(', ');
78
+ }
79
+ function describeActiveSkills(report) {
80
+ if (!report.stageSkillResolution) {
81
+ return 'unavailable';
82
+ }
83
+ const activeSkills = report.stageSkillResolution.considered_skills.filter((skill) => skill.active);
84
+ if (activeSkills.length === 0) {
85
+ return 'none';
86
+ }
87
+ return activeSkills.map((skill) => `${skill.id} [${skill.reason}]`).join(', ');
88
+ }
89
+ function describeVersion(versionStatus, presetMetadata) {
90
+ if (versionStatus.status === 'current') {
91
+ return `current (${presetMetadata.name}@${presetMetadata.version}, schema ${presetMetadata.schemaVersion})`;
92
+ }
93
+ if (versionStatus.status === 'outdated' && versionStatus.current) {
94
+ return `outdated (${versionStatus.current.presetName}@${versionStatus.current.presetVersion}, schema ${versionStatus.current.schemaVersion})`;
95
+ }
96
+ if (versionStatus.status === 'malformed') {
97
+ return 'malformed';
98
+ }
99
+ return 'missing';
100
+ }
101
+ function describeRuntime(runtime) {
102
+ if (!runtime) {
103
+ return 'unavailable';
104
+ }
105
+ if (runtime.status === RUNTIME_STATUS.NOT_BOOTSTRAPPED) {
106
+ return 'not bootstrapped';
107
+ }
108
+ const stage = runtime.current_stage ?? runtime.pending_stage ?? 'none';
109
+ const task = runtime.current_task_id ?? (runtime.pending_stage ? `${runtime.pending_stage} pending` : 'none');
110
+ return `${runtime.current_state} at ${stage} (${task})`;
111
+ }
112
+ function describeStageValidation(report) {
113
+ const runtime = report.runtimeState?.runtime ?? null;
114
+ if (!runtime) {
115
+ return 'unavailable';
116
+ }
117
+ if (runtime.current_state === 'failed' || runtime.last_validation_result === 'fail') {
118
+ const stage = runtime.failure_metadata?.stage ?? runtime.current_stage ?? null;
119
+ const reason = runtime.failure_metadata?.reason ?? runtime.blocked_reason ?? 'stage outputs failed contract validation';
120
+ return `failed${stage ? ` at ${stage}` : ''}: ${reason}`;
121
+ }
122
+ if (!runtime.last_validation) {
123
+ return 'not run yet';
124
+ }
125
+ return runtime.last_validation.passed
126
+ ? `last pass at ${runtime.last_validation.stage} (contract ${runtime.last_validation.contract_version})`
127
+ : `failed at ${runtime.last_validation.stage}`;
128
+ }
129
+ async function checkManualBootstrapGuidance(repoRoot) {
130
+ const agentsPath = resolveCanonicalPath(repoRoot, '.prodify/AGENTS.md');
131
+ if (!(await pathExists(agentsPath))) {
132
+ return false;
133
+ }
134
+ const content = await fs.readFile(agentsPath, 'utf8');
135
+ return hasManualBootstrapGuidance(content);
136
+ }
137
+ function deriveNextAction({ initialized, canonicalOk, contractsOk, configuredAgents, versionStatus, runtimeState, runtimeStateError, bootstrapPrompt }) {
138
+ if (!initialized) {
139
+ return 'prodify init';
140
+ }
141
+ if (!canonicalOk || !contractsOk || ['missing', 'malformed', 'outdated'].includes(versionStatus.status)) {
142
+ return 'prodify update';
143
+ }
144
+ if (runtimeStateError) {
145
+ return 'prodify update';
146
+ }
147
+ if (!runtimeState || runtimeState.runtime.status === RUNTIME_STATUS.NOT_BOOTSTRAPPED) {
148
+ if (configuredAgents.length === 0) {
149
+ return 'prodify setup-agent <agent>';
150
+ }
151
+ return `tell your agent: "${bootstrapPrompt}"`;
152
+ }
153
+ if (runtimeState.runtime.current_state === 'failed' || runtimeState.runtime.last_validation_result === 'fail') {
154
+ return 'rerun or remediate stage outputs';
155
+ }
156
+ const resume = getResumeDecision(runtimeState);
157
+ if (!resume.resumable) {
158
+ return runtimeState.runtime.status === RUNTIME_STATUS.COMPLETE ? 'none' : 'repair runtime state';
159
+ }
160
+ return resume.command ?? 'repair runtime state';
161
+ }
162
+ export async function inspectRepositoryStatus(repoRoot, options = {}) {
163
+ const preset = await loadDefaultPreset();
164
+ const prodifyPath = resolveRepoPath(repoRoot, '.prodify');
165
+ const initialized = await pathExists(prodifyPath);
166
+ const configuredAgents = listConfiguredAgents(await readGlobalAgentSetupState({
167
+ allowMissing: true
168
+ }));
169
+ const bootstrapProfile = (getRuntimeProfile(options.agent ?? null)?.name
170
+ ?? detectRuntimeAgentFromEnv()
171
+ ?? (configuredAgents.length === 1 ? configuredAgents[0] : 'codex'));
172
+ const bootstrapPrompt = buildBootstrapPrompt(bootstrapProfile);
173
+ if (!initialized) {
174
+ return {
175
+ ok: false,
176
+ initialized: false,
177
+ canonicalOk: false,
178
+ canonicalMissing: [...REQUIRED_CANONICAL_PATHS],
179
+ contractsOk: false,
180
+ contractInventory: null,
181
+ versionStatus: {
182
+ status: 'missing',
183
+ current: null,
184
+ expected: preset.metadata,
185
+ schemaMigrationRequired: false
186
+ },
187
+ configuredAgents,
188
+ runtimeState: null,
189
+ runtimeStateError: null,
190
+ resumable: false,
191
+ manualBootstrapReady: false,
192
+ bootstrapProfile,
193
+ bootstrapPrompt,
194
+ stageSkillResolution: null,
195
+ recommendedNextAction: 'prodify init',
196
+ presetMetadata: preset.metadata
197
+ };
198
+ }
199
+ const missingPaths = [];
200
+ for (const relativePath of REQUIRED_CANONICAL_PATHS) {
201
+ if (!(await pathExists(resolveCanonicalPath(repoRoot, relativePath)))) {
202
+ missingPaths.push(relativePath);
203
+ }
204
+ }
205
+ const contractInventory = await inspectCompiledContracts(repoRoot);
206
+ const versionStatus = await inspectVersionStatus(repoRoot, preset.metadata);
207
+ let runtimeState = null;
208
+ let runtimeStateError = null;
209
+ let stageSkillResolution = null;
210
+ try {
211
+ runtimeState = await readRuntimeState(repoRoot, {
212
+ presetMetadata: preset.metadata
213
+ });
214
+ }
215
+ catch (error) {
216
+ runtimeStateError = error instanceof Error ? error : new Error(String(error));
217
+ }
218
+ const manualBootstrapReady = await checkManualBootstrapGuidance(repoRoot);
219
+ const canonicalOk = missingPaths.length === 0;
220
+ if (canonicalOk && contractInventory.ok) {
221
+ const skillStage = runtimeState?.runtime.current_stage
222
+ ?? runtimeState?.runtime.pending_stage
223
+ ?? 'understand';
224
+ stageSkillResolution = await resolveStageSkills(repoRoot, skillStage);
225
+ }
226
+ const resume = runtimeState ? getResumeDecision(runtimeState) : {
227
+ resumable: false,
228
+ command: null,
229
+ reason: runtimeStateError?.message ?? 'runtime unavailable'
230
+ };
231
+ const stageValidationFailed = runtimeState?.runtime.current_state === 'failed'
232
+ || runtimeState?.runtime.last_validation_result === 'fail';
233
+ return {
234
+ ok: initialized
235
+ && canonicalOk
236
+ && contractInventory.ok
237
+ && versionStatus.status === 'current'
238
+ && !runtimeStateError
239
+ && manualBootstrapReady
240
+ && !stageValidationFailed,
241
+ initialized,
242
+ canonicalOk,
243
+ canonicalMissing: missingPaths,
244
+ contractsOk: contractInventory.ok,
245
+ contractInventory,
246
+ versionStatus,
247
+ configuredAgents,
248
+ runtimeState,
249
+ runtimeStateError,
250
+ resumable: resume.resumable,
251
+ manualBootstrapReady,
252
+ bootstrapProfile,
253
+ bootstrapPrompt,
254
+ stageSkillResolution,
255
+ recommendedNextAction: deriveNextAction({
256
+ initialized,
257
+ canonicalOk,
258
+ contractsOk: contractInventory.ok,
259
+ configuredAgents,
260
+ versionStatus,
261
+ runtimeState,
262
+ runtimeStateError,
263
+ bootstrapPrompt
264
+ }),
265
+ presetMetadata: preset.metadata
266
+ };
267
+ }
268
+ export function renderStatusReport(report) {
269
+ const lines = [
270
+ 'Prodify Status',
271
+ `Repository: ${report.initialized ? 'initialized' : 'not initialized'}`,
272
+ `Workspace health: ${describeWorkspaceHealth(report)}`,
273
+ `Canonical files: ${describeCanonicalHealth(report.canonicalMissing)}`,
274
+ `Contract freshness: ${describeContractFreshness(report)}`,
275
+ `Version/schema: ${describeVersion(report.versionStatus, report.presetMetadata)}`,
276
+ 'Repo runtime binding: agent-agnostic',
277
+ `Global agent setup: ${describeGlobalAgentSetup(report)}`,
278
+ `Skill routing stage: ${describeSkillStage(report)}`,
279
+ `Skills considered: ${describeConsideredSkills(report)}`,
280
+ `Skills active: ${describeActiveSkills(report)}`,
281
+ `Execution state: ${describeRuntime(report.runtimeState?.runtime ?? null)}`,
282
+ `Stage validation: ${describeStageValidation(report)}`,
283
+ `Manual bootstrap: ${report.manualBootstrapReady ? 'ready' : 'repair .prodify/AGENTS.md guidance'}`,
284
+ `Bootstrap profile: ${report.bootstrapProfile}`,
285
+ `Bootstrap prompt: ${report.bootstrapPrompt}`,
286
+ `Resumable: ${report.resumable ? 'yes' : 'no'}`,
287
+ `Recommended next action: ${report.recommendedNextAction}`
288
+ ];
289
+ if (report.runtimeStateError) {
290
+ lines.splice(7, 0, `Runtime state: ${report.runtimeStateError.message}`);
291
+ }
292
+ return lines.join('\n');
293
+ }
@@ -0,0 +1,63 @@
1
+ import fs from 'node:fs/promises';
2
+ import { ProdifyError } from './errors.js';
3
+ import { detectManagedFileState, parseManagedFileHeader } from './managed-files.js';
4
+ import { pathExists, writeFileEnsuringDir } from './fs.js';
5
+ import { resolveTargetPath } from './paths.js';
6
+ import { getLegacyTarget, listLegacyTargets } from '../legacy/targets.js';
7
+ export async function syncManagedTargets(repoRoot, options = {}) {
8
+ const requestedAgent = options.agent ?? null;
9
+ const force = options.force ?? false;
10
+ const createMissing = options.createMissing ?? Boolean(requestedAgent);
11
+ const requestedTarget = requestedAgent ? getLegacyTarget(requestedAgent) : null;
12
+ if (requestedAgent && !requestedTarget) {
13
+ throw new ProdifyError(`Unknown target agent: ${requestedAgent}`, {
14
+ code: 'UNKNOWN_TARGET'
15
+ });
16
+ }
17
+ const agents = requestedAgent
18
+ ? (requestedTarget ? [requestedTarget] : [])
19
+ : listLegacyTargets();
20
+ const results = [];
21
+ for (const target of agents) {
22
+ const agent = target.agent;
23
+ const targetPath = resolveTargetPath(repoRoot, agent);
24
+ if (!targetPath) {
25
+ continue;
26
+ }
27
+ if (!target.enabled || !target.generator) {
28
+ results.push({
29
+ agent,
30
+ targetPath,
31
+ status: 'not-enabled'
32
+ });
33
+ continue;
34
+ }
35
+ const expectedContent = await target.generator(repoRoot);
36
+ if (!(await pathExists(targetPath))) {
37
+ if (createMissing) {
38
+ await writeFileEnsuringDir(targetPath, expectedContent);
39
+ results.push({ agent, targetPath, status: 'updated' });
40
+ }
41
+ continue;
42
+ }
43
+ const existingContent = await fs.readFile(targetPath, 'utf8');
44
+ const managedHeader = parseManagedFileHeader(existingContent);
45
+ if (!managedHeader || managedHeader.targetAgent !== agent) {
46
+ results.push({ agent, targetPath, status: 'skipped-unmanaged' });
47
+ continue;
48
+ }
49
+ const state = detectManagedFileState(existingContent, expectedContent);
50
+ if (state.state === 'unchanged') {
51
+ results.push({ agent, targetPath, status: 'unchanged' });
52
+ continue;
53
+ }
54
+ if (state.state === 'conflict' && !force) {
55
+ results.push({ agent, targetPath, status: 'blocked' });
56
+ continue;
57
+ }
58
+ await writeFileEnsuringDir(targetPath, expectedContent);
59
+ results.push({ agent, targetPath, status: 'updated' });
60
+ }
61
+ results.sort((left, right) => left.agent.localeCompare(right.agent));
62
+ return results;
63
+ }
@@ -0,0 +1,48 @@
1
+ import { isRuntimeProfileName } from './paths.js';
2
+ export const RUNTIME_PROFILES = {
3
+ codex: {
4
+ name: 'codex',
5
+ displayName: 'Codex',
6
+ bootstrapPrompt: 'Read .prodify/AGENTS.md and bootstrap Prodify for this repository.',
7
+ bootstrapSummary: 'Manual bootstrap through .prodify/AGENTS.md.',
8
+ executeCommand: '$prodify-execute',
9
+ resumeCommand: '$prodify-resume',
10
+ nuances: ['Prefer the explicit instruction to read .prodify/AGENTS.md before any repo scan.']
11
+ },
12
+ claude: {
13
+ name: 'claude',
14
+ displayName: 'Claude',
15
+ bootstrapPrompt: 'Read .prodify/AGENTS.md and bootstrap Prodify for this repository. Then use the runtime commands from that file.',
16
+ bootstrapSummary: 'Manual bootstrap from canonical guidance inside .prodify/.',
17
+ executeCommand: '$prodify-execute',
18
+ resumeCommand: '$prodify-resume',
19
+ nuances: ['Keep the flow anchored to .prodify/AGENTS.md instead of relying on root discovery.']
20
+ },
21
+ copilot: {
22
+ name: 'copilot',
23
+ displayName: 'Copilot',
24
+ bootstrapPrompt: 'Read .prodify/AGENTS.md, bootstrap Prodify for this repository, and keep the workflow state under .prodify/.',
25
+ bootstrapSummary: 'Manual bootstrap with explicit .prodify-only state.',
26
+ executeCommand: '$prodify-execute',
27
+ resumeCommand: '$prodify-resume',
28
+ nuances: ['The main flow does not require .github/copilot-instructions.md.']
29
+ },
30
+ opencode: {
31
+ name: 'opencode',
32
+ displayName: 'OpenCode',
33
+ bootstrapPrompt: 'Read .prodify/AGENTS.md and bootstrap Prodify for this repository using the .prodify runtime state.',
34
+ bootstrapSummary: 'Manual bootstrap with runtime state anchored in .prodify/state.json.',
35
+ executeCommand: '$prodify-execute',
36
+ resumeCommand: '$prodify-resume',
37
+ nuances: ['No root-level OpenCode adapter is required for the default flow.']
38
+ }
39
+ };
40
+ export function listRuntimeProfiles() {
41
+ return Object.values(RUNTIME_PROFILES);
42
+ }
43
+ export function getRuntimeProfile(agent) {
44
+ if (!agent || !isRuntimeProfileName(agent)) {
45
+ return null;
46
+ }
47
+ return RUNTIME_PROFILES[agent];
48
+ }
@@ -0,0 +1,57 @@
1
+ import fs from 'node:fs/promises';
2
+ import { writeFileEnsuringDir } from './fs.js';
3
+ import { USER_OWNED_CANONICAL_PATHS, USER_OWNED_CANONICAL_PREFIXES, resolveRepoPath } from './paths.js';
4
+ import { loadDefaultPreset } from '../presets/loader.js';
5
+ import { readRuntimeState, createInitialRuntimeState, normalizeRuntimeState, serializeRuntimeState } from './state.js';
6
+ import { inspectVersionStatus } from './version-checks.js';
7
+ import { synchronizeRuntimeContracts } from '../contracts/compiler.js';
8
+ export async function updateProdifySetup(repoRoot) {
9
+ const preset = await loadDefaultPreset();
10
+ const versionStatus = await inspectVersionStatus(repoRoot, preset.metadata);
11
+ const writtenCanonical = [];
12
+ const preservedCanonical = [];
13
+ for (const entry of preset.entries) {
14
+ const targetPath = resolveRepoPath(repoRoot, entry.relativePath);
15
+ const isUserOwned = USER_OWNED_CANONICAL_PATHS.includes(entry.relativePath);
16
+ const isUserOwnedByPrefix = USER_OWNED_CANONICAL_PREFIXES.some((prefix) => entry.relativePath.startsWith(prefix));
17
+ if (isUserOwned || isUserOwnedByPrefix) {
18
+ try {
19
+ await fs.access(targetPath);
20
+ preservedCanonical.push(entry.relativePath);
21
+ continue;
22
+ }
23
+ catch {
24
+ // fall through and create the missing file
25
+ }
26
+ }
27
+ if (entry.relativePath === '.prodify/state.json') {
28
+ let nextState;
29
+ try {
30
+ const existingState = await readRuntimeState(repoRoot, {
31
+ presetMetadata: preset.metadata
32
+ });
33
+ nextState = normalizeRuntimeState(existingState, {
34
+ presetMetadata: preset.metadata
35
+ });
36
+ }
37
+ catch {
38
+ nextState = createInitialRuntimeState({
39
+ presetMetadata: preset.metadata
40
+ });
41
+ }
42
+ await writeFileEnsuringDir(targetPath, serializeRuntimeState(nextState));
43
+ writtenCanonical.push(entry.relativePath);
44
+ continue;
45
+ }
46
+ await writeFileEnsuringDir(targetPath, entry.content);
47
+ writtenCanonical.push(entry.relativePath);
48
+ }
49
+ const compiledContracts = await synchronizeRuntimeContracts(repoRoot);
50
+ return {
51
+ versionStatus: versionStatus.status,
52
+ schemaMigrationRequired: versionStatus.schemaMigrationRequired,
53
+ writtenCanonicalCount: writtenCanonical.length,
54
+ preservedCanonicalCount: preservedCanonical.length,
55
+ compiledContractCount: compiledContracts.length
56
+ };
57
+ }
@@ -0,0 +1,138 @@
1
+ import fs from 'node:fs/promises';
2
+ import { loadCompiledContract } from '../contracts/compiler.js';
3
+ import { normalizeRepoRelativePath, resolveRepoPath } from './paths.js';
4
+ function pathIsWithin(pathToCheck, root) {
5
+ const normalizedPath = normalizeRepoRelativePath(pathToCheck);
6
+ const normalizedRoot = normalizeRepoRelativePath(root);
7
+ return normalizedPath === normalizedRoot || normalizedPath.startsWith(`${normalizedRoot.replace(/\/$/, '')}/`);
8
+ }
9
+ function collectMarkdownSections(markdown) {
10
+ const sections = new Map();
11
+ const lines = markdown.replace(/\r\n/g, '\n').split('\n');
12
+ let currentHeading = null;
13
+ let buffer = [];
14
+ const flush = () => {
15
+ if (currentHeading) {
16
+ sections.set(currentHeading, buffer.join('\n').trim());
17
+ }
18
+ };
19
+ for (const line of lines) {
20
+ const headingMatch = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
21
+ if (headingMatch) {
22
+ flush();
23
+ currentHeading = headingMatch[2].trim();
24
+ buffer = [];
25
+ continue;
26
+ }
27
+ buffer.push(line);
28
+ }
29
+ flush();
30
+ return sections;
31
+ }
32
+ function containsAllStatements(sectionContent, statements) {
33
+ return statements.every((statement) => sectionContent.includes(statement));
34
+ }
35
+ function buildRule(rule, message, path) {
36
+ return { rule, message, path };
37
+ }
38
+ async function validateArtifact(repoRoot, contract, artifact) {
39
+ const artifactPath = resolveRepoPath(repoRoot, artifact.path);
40
+ const issues = [];
41
+ const missing = [];
42
+ const diagnostics = [];
43
+ try {
44
+ const content = await fs.readFile(artifactPath, 'utf8');
45
+ diagnostics.push(`validated artifact ${artifact.path}`);
46
+ if (!contract.allowed_write_roots.some((root) => pathIsWithin(artifact.path, root))) {
47
+ issues.push(buildRule('artifact/outside-allowed-roots', `Required artifact ${artifact.path} is outside allowed write roots.`, artifact.path));
48
+ }
49
+ if (artifact.format === 'markdown') {
50
+ const sections = collectMarkdownSections(content);
51
+ for (const section of artifact.required_sections) {
52
+ if (!sections.has(section)) {
53
+ issues.push(buildRule('artifact/markdown-section-missing', `Markdown artifact ${artifact.path} is missing section "${section}".`, artifact.path));
54
+ }
55
+ }
56
+ const successCriteriaSection = sections.get('Success Criteria') ?? '';
57
+ if (contract.success_criteria.length > 0 && !containsAllStatements(successCriteriaSection, contract.success_criteria)) {
58
+ issues.push(buildRule('artifact/success-criteria-missing', `Markdown artifact ${artifact.path} does not document all success criteria.`, artifact.path));
59
+ }
60
+ const policyChecksSection = sections.get('Policy Checks') ?? '';
61
+ if (contract.policy_rules.length > 0 && !containsAllStatements(policyChecksSection, contract.policy_rules)) {
62
+ issues.push(buildRule('artifact/policy-rules-missing', `Markdown artifact ${artifact.path} does not document all policy checks.`, artifact.path));
63
+ }
64
+ }
65
+ else {
66
+ const parsed = JSON.parse(content);
67
+ for (const key of artifact.required_json_keys) {
68
+ if (!(key in parsed)) {
69
+ issues.push(buildRule('artifact/json-key-missing', `JSON artifact ${artifact.path} is missing key "${key}".`, artifact.path));
70
+ }
71
+ }
72
+ }
73
+ }
74
+ catch {
75
+ missing.push(artifact.path);
76
+ issues.push(buildRule('artifact/missing', `Required artifact ${artifact.path} is missing.`, artifact.path));
77
+ }
78
+ return {
79
+ issues,
80
+ missing,
81
+ diagnostics
82
+ };
83
+ }
84
+ export async function validateStageOutputs(repoRoot, { contract, runtimeState, touchedPaths = [] }) {
85
+ const violatedRules = [];
86
+ const missingArtifacts = [];
87
+ const diagnostics = [];
88
+ const warnings = [];
89
+ if (runtimeState.runtime.current_stage !== contract.stage) {
90
+ violatedRules.push(buildRule('runtime/stage-mismatch', `Runtime stage ${runtimeState.runtime.current_stage ?? 'none'} does not match contract stage ${contract.stage}.`));
91
+ }
92
+ for (const artifact of contract.required_artifacts) {
93
+ const artifactValidation = await validateArtifact(repoRoot, contract, artifact);
94
+ violatedRules.push(...artifactValidation.issues);
95
+ missingArtifacts.push(...artifactValidation.missing);
96
+ diagnostics.push(...artifactValidation.diagnostics);
97
+ }
98
+ for (const rawTouchedPath of touchedPaths.map((entry) => normalizeRepoRelativePath(entry))) {
99
+ if (!contract.allowed_write_roots.some((root) => pathIsWithin(rawTouchedPath, root))) {
100
+ violatedRules.push(buildRule('writes/outside-allowed-roots', `Touched path ${rawTouchedPath} is outside the contract write boundary.`, rawTouchedPath));
101
+ }
102
+ if (contract.forbidden_writes.some((entry) => pathIsWithin(rawTouchedPath, entry))) {
103
+ violatedRules.push(buildRule('writes/forbidden', `Touched path ${rawTouchedPath} is forbidden by the contract.`, rawTouchedPath));
104
+ }
105
+ }
106
+ if (touchedPaths.length === 0) {
107
+ warnings.push('No touched paths were provided; forbidden-write checks are limited to required artifacts.');
108
+ }
109
+ return {
110
+ stage: contract.stage,
111
+ contract_version: contract.contract_version,
112
+ passed: violatedRules.length === 0,
113
+ violated_rules: violatedRules,
114
+ missing_artifacts: [...new Set(missingArtifacts)].sort((left, right) => left.localeCompare(right)),
115
+ warnings,
116
+ diagnostics
117
+ };
118
+ }
119
+ export async function validateStageOutputsForCurrentState(repoRoot, { runtimeState, touchedPaths = [] }) {
120
+ const stage = runtimeState.runtime.current_stage;
121
+ if (!stage) {
122
+ throw new Error('Cannot validate stage outputs when no stage is active.');
123
+ }
124
+ const contract = await loadCompiledContract(repoRoot, stage);
125
+ return validateStageOutputs(repoRoot, {
126
+ contract,
127
+ runtimeState,
128
+ touchedPaths
129
+ });
130
+ }
131
+ export async function validateStageOutputsForStage(repoRoot, { stage, runtimeState, touchedPaths = [] }) {
132
+ const contract = await loadCompiledContract(repoRoot, stage);
133
+ return validateStageOutputs(repoRoot, {
134
+ contract,
135
+ runtimeState,
136
+ touchedPaths
137
+ });
138
+ }
@@ -0,0 +1,35 @@
1
+ import fs from 'node:fs/promises';
2
+ import { pathExists } from './fs.js';
3
+ import { resolveCanonicalPath } from './paths.js';
4
+ import { parseVersionMetadata } from '../presets/version.js';
5
+ export async function inspectVersionStatus(repoRoot, presetMetadata) {
6
+ const versionPath = resolveCanonicalPath(repoRoot, '.prodify/version.json');
7
+ if (!(await pathExists(versionPath))) {
8
+ return {
9
+ status: 'missing',
10
+ current: null,
11
+ expected: presetMetadata,
12
+ schemaMigrationRequired: false
13
+ };
14
+ }
15
+ try {
16
+ const current = parseVersionMetadata(await fs.readFile(versionPath, 'utf8'));
17
+ const versionMatches = current.presetName === presetMetadata.name
18
+ && current.presetVersion === presetMetadata.version;
19
+ const schemaMatches = current.schemaVersion === presetMetadata.schemaVersion;
20
+ return {
21
+ status: versionMatches && schemaMatches ? 'current' : 'outdated',
22
+ current,
23
+ expected: presetMetadata,
24
+ schemaMigrationRequired: !schemaMatches
25
+ };
26
+ }
27
+ catch {
28
+ return {
29
+ status: 'malformed',
30
+ current: null,
31
+ expected: presetMetadata,
32
+ schemaMigrationRequired: true
33
+ };
34
+ }
35
+ }
@@ -0,0 +1,14 @@
1
+ import { buildManagedMarkdownOutput, readCanonicalMarkdown } from './shared.js';
2
+ import { LEGACY_TARGET_PATH_DEFINITIONS } from '../core/paths.js';
3
+ export async function generateClaudeContent(repoRoot) {
4
+ const target = LEGACY_TARGET_PATH_DEFINITIONS.claude;
5
+ const guidance = await readCanonicalMarkdown(repoRoot, target.canonicalSources[0]);
6
+ const runtime = await readCanonicalMarkdown(repoRoot, '.prodify/runtime-commands.md');
7
+ const body = `${guidance.trimEnd()}\n\n${runtime.trim()}\n`;
8
+ return buildManagedMarkdownOutput({
9
+ agent: target.agent,
10
+ canonicalSources: target.canonicalSources,
11
+ regenerateCommand: 'prodify update',
12
+ body
13
+ });
14
+ }
@@ -0,0 +1,14 @@
1
+ import { buildManagedMarkdownOutput, readCanonicalMarkdown } from './shared.js';
2
+ import { LEGACY_TARGET_PATH_DEFINITIONS } from '../core/paths.js';
3
+ export async function generateCodexContent(repoRoot) {
4
+ const target = LEGACY_TARGET_PATH_DEFINITIONS.codex;
5
+ const guidance = await readCanonicalMarkdown(repoRoot, target.canonicalSources[0]);
6
+ const runtime = await readCanonicalMarkdown(repoRoot, '.prodify/runtime-commands.md');
7
+ const body = `${guidance.trimEnd()}\n\n${runtime.trim()}\n`;
8
+ return buildManagedMarkdownOutput({
9
+ agent: target.agent,
10
+ canonicalSources: target.canonicalSources,
11
+ regenerateCommand: 'prodify update',
12
+ body
13
+ });
14
+ }