@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,27 @@
1
+ import { resolveRepoRoot } from '../core/repo-root.js';
2
+ import { installTarget } from '../core/install.js';
3
+ import { ProdifyError } from '../core/errors.js';
4
+ function parseAgent(args) {
5
+ const agentFlagIndex = args.indexOf('--agent');
6
+ if (agentFlagIndex === -1 || !args[agentFlagIndex + 1]) {
7
+ throw new ProdifyError('install requires --agent <target>.', {
8
+ code: 'AGENT_REQUIRED'
9
+ });
10
+ }
11
+ return args[agentFlagIndex + 1];
12
+ }
13
+ function hasForceFlag(args) {
14
+ return args.includes('--force');
15
+ }
16
+ export async function runInstallCommand(args, context) {
17
+ const repoRoot = await resolveRepoRoot({
18
+ cwd: context.cwd
19
+ });
20
+ const agent = parseAgent(args);
21
+ const result = await installTarget(repoRoot, agent, {
22
+ force: hasForceFlag(args)
23
+ });
24
+ const statusSuffix = result.status === 'supported' ? '' : ` (${result.status})`;
25
+ context.stdout.write(`legacy compatibility install ${result.agent}${statusSuffix}: updated ${result.targetPath}\n`);
26
+ return 0;
27
+ }
@@ -0,0 +1,23 @@
1
+ import { setupAgentIntegration } from '../core/agent-setup.js';
2
+ import { ProdifyError } from '../core/errors.js';
3
+ function parseAgent(args) {
4
+ const agent = args[0] ?? null;
5
+ if (!agent || args.length !== 1) {
6
+ throw new ProdifyError('setup-agent requires exactly one argument: <codex|claude|copilot|opencode>.', {
7
+ code: 'INVALID_AGENT'
8
+ });
9
+ }
10
+ return agent;
11
+ }
12
+ export async function runSetupAgentCommand(args, context) {
13
+ const agent = parseAgent(args);
14
+ const result = await setupAgentIntegration(agent);
15
+ context.stdout.write('Prodify Agent Setup\n');
16
+ context.stdout.write(`Agent: ${agent}\n`);
17
+ context.stdout.write(`Status: ${result.alreadyConfigured ? 'already configured globally; refreshed' : 'configured globally'}\n`);
18
+ context.stdout.write(`Configured agents: ${result.configuredAgents.join(', ')}\n`);
19
+ context.stdout.write(`Registry: ${result.statePath}\n`);
20
+ context.stdout.write('Repo impact: none\n');
21
+ context.stdout.write('Next step: run `prodify init` in a repository, then open that agent and use `$prodify-init`.\n');
22
+ return 0;
23
+ }
@@ -0,0 +1,28 @@
1
+ import { resolveRepoRoot } from '../core/repo-root.js';
2
+ import { inspectRepositoryStatus, renderStatusReport } from '../core/status.js';
3
+ import { ProdifyError } from '../core/errors.js';
4
+ import { getRuntimeProfile } from '../core/targets.js';
5
+ function parseRequestedAgent(args) {
6
+ const agentFlagIndex = args.indexOf('--agent');
7
+ if (agentFlagIndex === -1) {
8
+ return null;
9
+ }
10
+ const value = args[agentFlagIndex + 1] ?? null;
11
+ if (!value || !getRuntimeProfile(value)) {
12
+ throw new ProdifyError('status requires --agent <codex|claude|copilot|opencode> when an agent is specified.', {
13
+ code: 'INVALID_AGENT'
14
+ });
15
+ }
16
+ return value;
17
+ }
18
+ export async function runStatusCommand(args, context) {
19
+ const repoRoot = await resolveRepoRoot({
20
+ cwd: context.cwd,
21
+ allowBootstrap: true
22
+ });
23
+ const report = await inspectRepositoryStatus(repoRoot, {
24
+ agent: parseRequestedAgent(args)
25
+ });
26
+ context.stdout.write(`${renderStatusReport(report)}\n`);
27
+ return report.ok ? 0 : 1;
28
+ }
@@ -0,0 +1,29 @@
1
+ import { resolveRepoRoot } from '../core/repo-root.js';
2
+ import { syncManagedTargets } from '../core/sync.js';
3
+ function parseAgent(args) {
4
+ const agentFlagIndex = args.indexOf('--agent');
5
+ if (agentFlagIndex === -1) {
6
+ return null;
7
+ }
8
+ return args[agentFlagIndex + 1] ?? null;
9
+ }
10
+ function hasForceFlag(args) {
11
+ return args.includes('--force');
12
+ }
13
+ export async function runSyncCommand(args, context) {
14
+ const repoRoot = await resolveRepoRoot({
15
+ cwd: context.cwd
16
+ });
17
+ const results = await syncManagedTargets(repoRoot, {
18
+ agent: parseAgent(args),
19
+ force: hasForceFlag(args)
20
+ });
21
+ if (results.length === 0) {
22
+ context.stdout.write('legacy compatibility sync: no managed targets found\n');
23
+ return 0;
24
+ }
25
+ for (const result of results) {
26
+ context.stdout.write(`legacy sync ${result.agent}: ${result.status} ${result.targetPath}\n`);
27
+ }
28
+ return 0;
29
+ }
@@ -0,0 +1,18 @@
1
+ import { resolveRepoRoot } from '../core/repo-root.js';
2
+ import { updateProdifySetup } from '../core/upgrade.js';
3
+ export async function runUpdateCommand(args, context) {
4
+ void args;
5
+ const repoRoot = await resolveRepoRoot({
6
+ cwd: context.cwd
7
+ });
8
+ const summary = await updateProdifySetup(repoRoot);
9
+ context.stdout.write('Prodify Update\n');
10
+ context.stdout.write(`Version/schema: ${summary.versionStatus}\n`);
11
+ if (summary.schemaMigrationRequired) {
12
+ context.stdout.write('Schema migration: applied\n');
13
+ }
14
+ context.stdout.write(`Canonical assets: ${summary.writtenCanonicalCount} written, ${summary.preservedCanonicalCount} preserved\n`);
15
+ context.stdout.write(`Compiled contracts: ${summary.compiledContractCount}\n`);
16
+ context.stdout.write('Legacy compatibility adapters: not part of the default flow\n');
17
+ return 0;
18
+ }
@@ -0,0 +1,37 @@
1
+ import { ProdifyError } from '../core/errors.js';
2
+ import { normalizeSourceContractDocument } from './source-schema.js';
3
+ function asString(value, fieldName) {
4
+ if (typeof value !== 'string' || value.trim() === '') {
5
+ throw new ProdifyError(`Compiled contract field "${fieldName}" must be a non-empty string.`, {
6
+ code: 'COMPILED_CONTRACT_INVALID'
7
+ });
8
+ }
9
+ return value.trim();
10
+ }
11
+ export function validateCompiledContractShape(contract) {
12
+ if (typeof contract !== 'object' || contract === null || Array.isArray(contract)) {
13
+ throw new ProdifyError('Compiled contract must be a JSON object.', {
14
+ code: 'COMPILED_CONTRACT_INVALID'
15
+ });
16
+ }
17
+ const record = contract;
18
+ return normalizeSourceContractDocument({
19
+ document: {
20
+ frontmatter: {
21
+ schema_version: record.schema_version,
22
+ contract_version: record.contract_version,
23
+ stage: record.stage,
24
+ task_id: record.task_id,
25
+ required_artifacts: record.required_artifacts,
26
+ allowed_write_roots: record.allowed_write_roots,
27
+ forbidden_writes: record.forbidden_writes,
28
+ policy_rules: record.policy_rules,
29
+ success_criteria: record.success_criteria,
30
+ skill_routing: record.skill_routing
31
+ },
32
+ body: 'compiled-contract'
33
+ },
34
+ sourcePath: asString(record.source_path, 'source_path'),
35
+ sourceHash: asString(record.source_hash, 'source_hash')
36
+ });
37
+ }
@@ -0,0 +1,83 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import { listFilesRecursive, pathExists, writeFileEnsuringDir } from '../core/fs.js';
4
+ import { normalizeRepoRelativePath, resolveCanonicalPath } from '../core/paths.js';
5
+ import { ProdifyError } from '../core/errors.js';
6
+ import { parseContractSource } from './parser.js';
7
+ import { validateCompiledContractShape } from './compiled-schema.js';
8
+ import { CONTRACT_STAGE_NAMES, normalizeSourceContractDocument } from './source-schema.js';
9
+ function createSourceHash(source) {
10
+ return crypto.createHash('sha256').update(source).digest('hex');
11
+ }
12
+ export function serializeCompiledContract(contract) {
13
+ return `${JSON.stringify(contract, null, 2)}\n`;
14
+ }
15
+ export function compileContractSource(options) {
16
+ const { markdown, sourcePath } = options;
17
+ const document = parseContractSource(markdown);
18
+ return normalizeSourceContractDocument({
19
+ document,
20
+ sourcePath,
21
+ sourceHash: createSourceHash(markdown.replace(/\r\n/g, '\n'))
22
+ });
23
+ }
24
+ export async function compileContractsFromSourceDir(repoRoot) {
25
+ const sourceDir = resolveCanonicalPath(repoRoot, '.prodify/contracts-src');
26
+ if (!(await pathExists(sourceDir))) {
27
+ throw new ProdifyError('Contract source directory is missing: .prodify/contracts-src', {
28
+ code: 'CONTRACT_SOURCE_MISSING'
29
+ });
30
+ }
31
+ const files = (await listFilesRecursive(sourceDir))
32
+ .filter((file) => file.relativePath.endsWith('.contract.md'))
33
+ .sort((left, right) => left.relativePath.localeCompare(right.relativePath));
34
+ const contracts = [];
35
+ for (const file of files) {
36
+ const sourcePath = `.prodify/contracts-src/${normalizeRepoRelativePath(file.relativePath)}`;
37
+ contracts.push(compileContractSource({
38
+ markdown: await fs.readFile(file.fullPath, 'utf8'),
39
+ sourcePath
40
+ }));
41
+ }
42
+ const seenStages = new Set();
43
+ for (const contract of contracts) {
44
+ if (seenStages.has(contract.stage)) {
45
+ throw new ProdifyError(`Duplicate contract stage detected: ${contract.stage}.`, {
46
+ code: 'CONTRACT_SCHEMA_INVALID'
47
+ });
48
+ }
49
+ seenStages.add(contract.stage);
50
+ }
51
+ return contracts.sort((left, right) => left.stage.localeCompare(right.stage));
52
+ }
53
+ export async function synchronizeRuntimeContracts(repoRoot) {
54
+ const contracts = await compileContractsFromSourceDir(repoRoot);
55
+ const compiledDir = resolveCanonicalPath(repoRoot, '.prodify/contracts');
56
+ await writeFileEnsuringDir(resolveCanonicalPath(repoRoot, '.prodify/contracts/README.md'), '# Compiled Contracts\n\nThis directory contains deterministic runtime-only JSON contracts generated from `.prodify/contracts-src/`.\n');
57
+ for (const contract of contracts) {
58
+ await writeFileEnsuringDir(resolveCanonicalPath(repoRoot, `.prodify/contracts/${contract.stage}.contract.json`), serializeCompiledContract(contract));
59
+ }
60
+ const existingFiles = (await pathExists(compiledDir))
61
+ ? await listFilesRecursive(compiledDir)
62
+ : [];
63
+ for (const file of existingFiles) {
64
+ if (!file.relativePath.endsWith('.contract.json')) {
65
+ continue;
66
+ }
67
+ const stage = file.relativePath.replace(/\.contract\.json$/, '');
68
+ if (!CONTRACT_STAGE_NAMES.includes(stage)) {
69
+ await fs.rm(file.fullPath, { force: true });
70
+ }
71
+ }
72
+ return contracts;
73
+ }
74
+ export async function loadCompiledContract(repoRoot, stage) {
75
+ const contractPath = resolveCanonicalPath(repoRoot, `.prodify/contracts/${stage}.contract.json`);
76
+ if (!(await pathExists(contractPath))) {
77
+ throw new ProdifyError(`Compiled contract is missing for stage "${stage}".`, {
78
+ code: 'COMPILED_CONTRACT_MISSING'
79
+ });
80
+ }
81
+ const parsed = JSON.parse(await fs.readFile(contractPath, 'utf8'));
82
+ return validateCompiledContractShape(parsed);
83
+ }
@@ -0,0 +1,52 @@
1
+ import { compileContractsFromSourceDir, loadCompiledContract, serializeCompiledContract } from './compiler.js';
2
+ import { CONTRACT_STAGE_NAMES } from './source-schema.js';
3
+ export async function inspectCompiledContracts(repoRoot) {
4
+ const inventory = {
5
+ ok: true,
6
+ sourceCount: 0,
7
+ compiledCount: 0,
8
+ staleStages: [],
9
+ missingCompiledStages: [],
10
+ missingSourceStages: [],
11
+ invalidStages: []
12
+ };
13
+ let expectedContracts = [];
14
+ try {
15
+ expectedContracts = await compileContractsFromSourceDir(repoRoot);
16
+ inventory.sourceCount = expectedContracts.length;
17
+ }
18
+ catch (error) {
19
+ inventory.ok = false;
20
+ inventory.invalidStages.push(error instanceof Error ? error.message : String(error));
21
+ }
22
+ const expectedByStage = new Map();
23
+ for (const contract of expectedContracts) {
24
+ expectedByStage.set(contract.stage, contract);
25
+ }
26
+ for (const stage of CONTRACT_STAGE_NAMES) {
27
+ const expected = expectedByStage.get(stage);
28
+ if (!expected) {
29
+ inventory.ok = false;
30
+ inventory.missingSourceStages.push(stage);
31
+ continue;
32
+ }
33
+ try {
34
+ const compiled = await loadCompiledContract(repoRoot, stage);
35
+ inventory.compiledCount += 1;
36
+ if (serializeCompiledContract(compiled) !== serializeCompiledContract(expected)) {
37
+ inventory.ok = false;
38
+ inventory.staleStages.push(stage);
39
+ }
40
+ }
41
+ catch (error) {
42
+ inventory.ok = false;
43
+ inventory.missingCompiledStages.push(stage);
44
+ inventory.invalidStages.push(error instanceof Error ? error.message : String(error));
45
+ }
46
+ }
47
+ inventory.staleStages.sort((left, right) => left.localeCompare(right));
48
+ inventory.missingCompiledStages.sort((left, right) => left.localeCompare(right));
49
+ inventory.missingSourceStages.sort((left, right) => left.localeCompare(right));
50
+ inventory.invalidStages.sort((left, right) => left.localeCompare(right));
51
+ return inventory;
52
+ }
@@ -0,0 +1,5 @@
1
+ export { parseContractSource } from './parser.js';
2
+ export { normalizeSourceContractDocument, CONTRACT_STAGE_NAMES } from './source-schema.js';
3
+ export { validateCompiledContractShape } from './compiled-schema.js';
4
+ export { compileContractSource, compileContractsFromSourceDir, synchronizeRuntimeContracts, loadCompiledContract, serializeCompiledContract } from './compiler.js';
5
+ export { inspectCompiledContracts } from './freshness.js';
@@ -0,0 +1,201 @@
1
+ import { ProdifyError } from '../core/errors.js';
2
+ function parseScalar(rawValue) {
3
+ const value = rawValue.trim();
4
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\''))) {
5
+ return value.slice(1, -1);
6
+ }
7
+ if (value === 'true') {
8
+ return true;
9
+ }
10
+ if (value === 'false') {
11
+ return false;
12
+ }
13
+ if (value === 'null') {
14
+ return null;
15
+ }
16
+ if (/^-?\d+(\.\d+)?$/.test(value)) {
17
+ return Number(value);
18
+ }
19
+ return value;
20
+ }
21
+ function splitKeyValue(content, lineNumber) {
22
+ const separatorIndex = content.indexOf(':');
23
+ if (separatorIndex === -1) {
24
+ throw new ProdifyError(`Invalid frontmatter at line ${lineNumber}: expected "key: value".`, {
25
+ code: 'CONTRACT_FRONTMATTER_INVALID'
26
+ });
27
+ }
28
+ const key = content.slice(0, separatorIndex).trim();
29
+ if (!key) {
30
+ throw new ProdifyError(`Invalid frontmatter at line ${lineNumber}: missing key before ":".`, {
31
+ code: 'CONTRACT_FRONTMATTER_INVALID'
32
+ });
33
+ }
34
+ return {
35
+ key,
36
+ value: content.slice(separatorIndex + 1).trim()
37
+ };
38
+ }
39
+ function preprocessFrontmatter(frontmatter) {
40
+ return frontmatter
41
+ .split('\n')
42
+ .map((line, index) => ({ raw: line.replace(/\r$/, ''), lineNumber: index + 1 }))
43
+ .filter(({ raw }) => raw.trim() !== '' && !raw.trimStart().startsWith('#'))
44
+ .map(({ raw, lineNumber }) => {
45
+ const indent = raw.match(/^ */)?.[0].length ?? 0;
46
+ if (indent % 2 !== 0) {
47
+ throw new ProdifyError(`Invalid frontmatter indentation at line ${lineNumber}: use multiples of two spaces.`, {
48
+ code: 'CONTRACT_FRONTMATTER_INVALID'
49
+ });
50
+ }
51
+ return {
52
+ indent,
53
+ content: raw.trim(),
54
+ lineNumber
55
+ };
56
+ });
57
+ }
58
+ function parseBlock(lines, state, indent) {
59
+ const line = lines[state.index];
60
+ if (!line || line.indent !== indent) {
61
+ throw new ProdifyError(`Invalid frontmatter near line ${line?.lineNumber ?? 'EOF'}: unexpected indentation.`, {
62
+ code: 'CONTRACT_FRONTMATTER_INVALID'
63
+ });
64
+ }
65
+ if (line.content.startsWith('- ')) {
66
+ return parseSequence(lines, state, indent);
67
+ }
68
+ return parseMapping(lines, state, indent);
69
+ }
70
+ function parseMapping(lines, state, indent) {
71
+ const value = {};
72
+ while (state.index < lines.length) {
73
+ const line = lines[state.index];
74
+ if (line.indent < indent) {
75
+ break;
76
+ }
77
+ if (line.indent > indent) {
78
+ throw new ProdifyError(`Invalid frontmatter at line ${line.lineNumber}: unexpected indentation.`, {
79
+ code: 'CONTRACT_FRONTMATTER_INVALID'
80
+ });
81
+ }
82
+ if (line.content.startsWith('- ')) {
83
+ throw new ProdifyError(`Invalid frontmatter at line ${line.lineNumber}: sequence item is not valid here.`, {
84
+ code: 'CONTRACT_FRONTMATTER_INVALID'
85
+ });
86
+ }
87
+ const { key, value: rawValue } = splitKeyValue(line.content, line.lineNumber);
88
+ state.index += 1;
89
+ if (rawValue !== '') {
90
+ value[key] = parseScalar(rawValue);
91
+ continue;
92
+ }
93
+ const nextLine = lines[state.index];
94
+ if (!nextLine || nextLine.indent <= indent) {
95
+ throw new ProdifyError(`Invalid frontmatter at line ${line.lineNumber}: expected an indented block for "${key}".`, {
96
+ code: 'CONTRACT_FRONTMATTER_INVALID'
97
+ });
98
+ }
99
+ value[key] = parseBlock(lines, state, indent + 2);
100
+ }
101
+ return value;
102
+ }
103
+ function parseSequence(lines, state, indent) {
104
+ const value = [];
105
+ while (state.index < lines.length) {
106
+ const line = lines[state.index];
107
+ if (line.indent < indent) {
108
+ break;
109
+ }
110
+ if (line.indent > indent) {
111
+ throw new ProdifyError(`Invalid frontmatter at line ${line.lineNumber}: unexpected indentation.`, {
112
+ code: 'CONTRACT_FRONTMATTER_INVALID'
113
+ });
114
+ }
115
+ if (!line.content.startsWith('- ')) {
116
+ break;
117
+ }
118
+ const inlineValue = line.content.slice(2).trim();
119
+ state.index += 1;
120
+ if (inlineValue === '') {
121
+ const nextLine = lines[state.index];
122
+ if (!nextLine || nextLine.indent <= indent) {
123
+ throw new ProdifyError(`Invalid frontmatter at line ${line.lineNumber}: expected an indented sequence value.`, {
124
+ code: 'CONTRACT_FRONTMATTER_INVALID'
125
+ });
126
+ }
127
+ value.push(parseBlock(lines, state, indent + 2));
128
+ continue;
129
+ }
130
+ if (inlineValue.includes(':')) {
131
+ const { key, value: rawValue } = splitKeyValue(inlineValue, line.lineNumber);
132
+ const item = {};
133
+ if (rawValue !== '') {
134
+ item[key] = parseScalar(rawValue);
135
+ }
136
+ else {
137
+ const nextLine = lines[state.index];
138
+ if (!nextLine || nextLine.indent <= indent) {
139
+ throw new ProdifyError(`Invalid frontmatter at line ${line.lineNumber}: expected an indented block for "${key}".`, {
140
+ code: 'CONTRACT_FRONTMATTER_INVALID'
141
+ });
142
+ }
143
+ item[key] = parseBlock(lines, state, indent + 2);
144
+ }
145
+ if (state.index < lines.length && lines[state.index].indent > indent) {
146
+ Object.assign(item, parseMapping(lines, state, indent + 2));
147
+ }
148
+ value.push(item);
149
+ continue;
150
+ }
151
+ value.push(parseScalar(inlineValue));
152
+ }
153
+ return value;
154
+ }
155
+ function parseFrontmatter(frontmatter) {
156
+ const lines = preprocessFrontmatter(frontmatter);
157
+ if (lines.length === 0) {
158
+ throw new ProdifyError('Contract source frontmatter is empty.', {
159
+ code: 'CONTRACT_FRONTMATTER_INVALID'
160
+ });
161
+ }
162
+ const state = { index: 0 };
163
+ const parsed = parseBlock(lines, state, 0);
164
+ if (state.index !== lines.length) {
165
+ const nextLine = lines[state.index];
166
+ throw new ProdifyError(`Invalid frontmatter at line ${nextLine.lineNumber}: trailing content could not be parsed.`, {
167
+ code: 'CONTRACT_FRONTMATTER_INVALID'
168
+ });
169
+ }
170
+ if (Array.isArray(parsed)) {
171
+ throw new ProdifyError('Contract source frontmatter must be a mapping.', {
172
+ code: 'CONTRACT_FRONTMATTER_INVALID'
173
+ });
174
+ }
175
+ return parsed;
176
+ }
177
+ export function parseContractSource(markdown) {
178
+ const normalized = markdown.replace(/\r\n/g, '\n');
179
+ if (!normalized.startsWith('---\n')) {
180
+ throw new ProdifyError('Contract source must start with YAML frontmatter delimited by "---".', {
181
+ code: 'CONTRACT_FRONTMATTER_MISSING'
182
+ });
183
+ }
184
+ const closingIndex = normalized.indexOf('\n---\n', 4);
185
+ if (closingIndex === -1) {
186
+ throw new ProdifyError('Contract source frontmatter is not closed with "---".', {
187
+ code: 'CONTRACT_FRONTMATTER_MISSING'
188
+ });
189
+ }
190
+ const rawFrontmatter = normalized.slice(4, closingIndex);
191
+ const body = normalized.slice(closingIndex + 5).trim();
192
+ if (!body) {
193
+ throw new ProdifyError('Contract source body is empty.', {
194
+ code: 'CONTRACT_BODY_EMPTY'
195
+ });
196
+ }
197
+ return {
198
+ frontmatter: parseFrontmatter(rawFrontmatter),
199
+ body
200
+ };
201
+ }
@@ -0,0 +1,138 @@
1
+ import { normalizeRepoRelativePath } from '../core/paths.js';
2
+ import { ProdifyError } from '../core/errors.js';
3
+ import { stageToTaskId } from '../core/flow-state.js';
4
+ const STAGES = ['understand', 'diagnose', 'architecture', 'plan', 'refactor', 'validate'];
5
+ function asString(value, fieldName) {
6
+ if (typeof value !== 'string' || value.trim() === '') {
7
+ throw new ProdifyError(`Contract frontmatter field "${fieldName}" must be a non-empty string.`, {
8
+ code: 'CONTRACT_SCHEMA_INVALID'
9
+ });
10
+ }
11
+ return value.trim();
12
+ }
13
+ function asStringArray(value, fieldName) {
14
+ if (!Array.isArray(value) || value.length === 0) {
15
+ throw new ProdifyError(`Contract frontmatter field "${fieldName}" must be a non-empty list.`, {
16
+ code: 'CONTRACT_SCHEMA_INVALID'
17
+ });
18
+ }
19
+ const normalized = value.map((entry) => asString(entry, fieldName));
20
+ return [...new Set(normalized)].sort((left, right) => left.localeCompare(right));
21
+ }
22
+ function asOptionalStringArray(value, fieldName) {
23
+ if (!Array.isArray(value)) {
24
+ return [];
25
+ }
26
+ if (value.length === 0) {
27
+ return [];
28
+ }
29
+ return asStringArray(value, fieldName);
30
+ }
31
+ function asStage(value) {
32
+ const stage = asString(value, 'stage');
33
+ if (!STAGES.includes(stage)) {
34
+ throw new ProdifyError(`Contract frontmatter field "stage" is invalid: ${stage}.`, {
35
+ code: 'CONTRACT_SCHEMA_INVALID'
36
+ });
37
+ }
38
+ return stage;
39
+ }
40
+ function normalizeArtifactRule(rawValue) {
41
+ if (typeof rawValue !== 'object' || rawValue === null || Array.isArray(rawValue)) {
42
+ throw new ProdifyError('Each required_artifacts entry must be a mapping.', {
43
+ code: 'CONTRACT_SCHEMA_INVALID'
44
+ });
45
+ }
46
+ const record = rawValue;
47
+ const path = normalizeRepoRelativePath(asString(record.path, 'required_artifacts.path'));
48
+ const format = asString(record.format, 'required_artifacts.format');
49
+ if (format !== 'markdown' && format !== 'json') {
50
+ throw new ProdifyError(`Unsupported artifact format: ${format}.`, {
51
+ code: 'CONTRACT_SCHEMA_INVALID'
52
+ });
53
+ }
54
+ const requiredSections = asOptionalStringArray(record.required_sections, 'required_artifacts.required_sections');
55
+ const requiredJsonKeys = asOptionalStringArray(record.required_json_keys, 'required_artifacts.required_json_keys');
56
+ if (format === 'markdown' && requiredSections.length === 0) {
57
+ throw new ProdifyError(`Markdown artifact "${path}" must declare required_sections.`, {
58
+ code: 'CONTRACT_SCHEMA_INVALID'
59
+ });
60
+ }
61
+ if (format === 'json' && requiredJsonKeys.length === 0) {
62
+ throw new ProdifyError(`JSON artifact "${path}" must declare required_json_keys.`, {
63
+ code: 'CONTRACT_SCHEMA_INVALID'
64
+ });
65
+ }
66
+ return {
67
+ path,
68
+ format,
69
+ required_sections: requiredSections,
70
+ required_json_keys: requiredJsonKeys
71
+ };
72
+ }
73
+ export function buildCompiledContract(options) {
74
+ const { document, sourcePath, sourceHash } = options;
75
+ const stage = asStage(document.frontmatter.stage);
76
+ const taskId = asString(document.frontmatter.task_id, 'task_id');
77
+ if (taskId !== stageToTaskId(stage)) {
78
+ throw new ProdifyError(`Contract task_id "${taskId}" does not match stage "${stage}".`, {
79
+ code: 'CONTRACT_SCHEMA_INVALID'
80
+ });
81
+ }
82
+ if (document.body.trim().length === 0) {
83
+ throw new ProdifyError(`Contract source "${sourcePath}" must contain explanatory body content.`, {
84
+ code: 'CONTRACT_BODY_EMPTY'
85
+ });
86
+ }
87
+ const rawArtifacts = document.frontmatter.required_artifacts;
88
+ if (!Array.isArray(rawArtifacts) || rawArtifacts.length === 0) {
89
+ throw new ProdifyError('Contract frontmatter field "required_artifacts" must contain at least one artifact rule.', {
90
+ code: 'CONTRACT_SCHEMA_INVALID'
91
+ });
92
+ }
93
+ const requiredArtifacts = rawArtifacts
94
+ .map((artifact) => normalizeArtifactRule(artifact))
95
+ .sort((left, right) => left.path.localeCompare(right.path));
96
+ return {
97
+ schema_version: String(document.frontmatter.schema_version ?? '1'),
98
+ contract_version: asString(document.frontmatter.contract_version, 'contract_version'),
99
+ stage,
100
+ task_id: taskId,
101
+ source_path: normalizeRepoRelativePath(sourcePath),
102
+ source_hash: sourceHash,
103
+ required_artifacts: requiredArtifacts,
104
+ allowed_write_roots: asStringArray(document.frontmatter.allowed_write_roots, 'allowed_write_roots')
105
+ .map((entry) => normalizeRepoRelativePath(entry)),
106
+ forbidden_writes: Array.isArray(document.frontmatter.forbidden_writes)
107
+ ? asStringArray(document.frontmatter.forbidden_writes, 'forbidden_writes').map((entry) => normalizeRepoRelativePath(entry))
108
+ : [],
109
+ policy_rules: asStringArray(document.frontmatter.policy_rules, 'policy_rules'),
110
+ success_criteria: asStringArray(document.frontmatter.success_criteria, 'success_criteria')
111
+ };
112
+ }
113
+ export function validateCompiledContractShape(contract) {
114
+ if (typeof contract !== 'object' || contract === null || Array.isArray(contract)) {
115
+ throw new ProdifyError('Compiled contract must be a JSON object.', {
116
+ code: 'COMPILED_CONTRACT_INVALID'
117
+ });
118
+ }
119
+ const record = contract;
120
+ return buildCompiledContract({
121
+ document: {
122
+ frontmatter: {
123
+ schema_version: record.schema_version,
124
+ contract_version: record.contract_version,
125
+ stage: record.stage,
126
+ task_id: record.task_id,
127
+ required_artifacts: record.required_artifacts,
128
+ allowed_write_roots: record.allowed_write_roots,
129
+ forbidden_writes: record.forbidden_writes,
130
+ policy_rules: record.policy_rules,
131
+ success_criteria: record.success_criteria
132
+ },
133
+ body: 'compiled-contract'
134
+ },
135
+ sourcePath: asString(record.source_path, 'source_path'),
136
+ sourceHash: asString(record.source_hash, 'source_hash')
137
+ });
138
+ }