@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,74 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import { ProdifyError } from './errors.js';
5
+
6
+ interface ResolveRepoRootOptions {
7
+ cwd?: string;
8
+ repoRoot?: string;
9
+ allowBootstrap?: boolean;
10
+ }
11
+
12
+ async function isDirectory(targetPath: string): Promise<boolean> {
13
+ try {
14
+ return (await fs.stat(targetPath)).isDirectory();
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ async function directoryHas(directory: string, childName: string): Promise<boolean> {
21
+ return isDirectory(path.join(directory, childName));
22
+ }
23
+
24
+ async function searchUpwards(startDir: string, predicate: (candidate: string) => Promise<boolean>): Promise<string | null> {
25
+ let current = path.resolve(startDir);
26
+
27
+ while (true) {
28
+ if (await predicate(current)) {
29
+ return current;
30
+ }
31
+
32
+ const parent = path.dirname(current);
33
+ if (parent === current) {
34
+ return null;
35
+ }
36
+
37
+ current = parent;
38
+ }
39
+ }
40
+
41
+ export async function resolveRepoRoot(options: ResolveRepoRootOptions = {}): Promise<string> {
42
+ const cwd = path.resolve(options.cwd ?? process.cwd());
43
+ const explicitRepo = options.repoRoot ? path.resolve(options.repoRoot) : null;
44
+ const allowBootstrap = options.allowBootstrap ?? false;
45
+
46
+ if (explicitRepo) {
47
+ const hasProdify = await directoryHas(explicitRepo, '.prodify');
48
+ const hasGit = await directoryHas(explicitRepo, '.git');
49
+
50
+ if (!hasProdify && !(allowBootstrap && hasGit)) {
51
+ throw new ProdifyError(`Could not verify repository root at ${explicitRepo}.`, {
52
+ code: 'REPO_ROOT_NOT_FOUND'
53
+ });
54
+ }
55
+
56
+ return explicitRepo;
57
+ }
58
+
59
+ const prodifyRoot = await searchUpwards(cwd, async (candidate) => directoryHas(candidate, '.prodify'));
60
+ if (prodifyRoot) {
61
+ return prodifyRoot;
62
+ }
63
+
64
+ if (allowBootstrap) {
65
+ const gitRoot = await searchUpwards(cwd, async (candidate) => directoryHas(candidate, '.git'));
66
+ if (gitRoot) {
67
+ return gitRoot;
68
+ }
69
+ }
70
+
71
+ throw new ProdifyError('Could not resolve repository root from the current working directory.', {
72
+ code: 'REPO_ROOT_NOT_FOUND'
73
+ });
74
+ }
@@ -0,0 +1,151 @@
1
+ import { loadCompiledContract } from '../contracts/compiler.js';
2
+ import { ProdifyError } from './errors.js';
3
+ import { detectRepoContext } from './repo-context.js';
4
+ import { loadSkillRegistry } from '../skills/loader.js';
5
+ import type {
6
+ FlowStage,
7
+ RepoContextFact,
8
+ RepoContextSnapshot,
9
+ SkillActivationRecord,
10
+ SkillCondition,
11
+ SkillDefinition,
12
+ StageSkillResolution,
13
+ StageSkillRouting
14
+ } from '../types.js';
15
+
16
+ function factValues(context: RepoContextSnapshot, fact: RepoContextFact): string[] {
17
+ switch (fact) {
18
+ case 'language':
19
+ return context.languages;
20
+ case 'framework':
21
+ return context.frameworks;
22
+ case 'project_type':
23
+ return context.project_types;
24
+ case 'architecture_pattern':
25
+ return context.architecture_patterns;
26
+ case 'risk_signal':
27
+ return context.risk_signals;
28
+ default:
29
+ return [];
30
+ }
31
+ }
32
+
33
+ function formatCondition(condition: SkillCondition): string {
34
+ return condition.all.map((predicate) => `${predicate.fact}=${predicate.includes}`).join(' and ');
35
+ }
36
+
37
+ function formatSkillActivationConditions(skill: SkillDefinition): string {
38
+ return skill.activation_conditions.map((condition) => formatCondition(condition)).join(' or ');
39
+ }
40
+
41
+ function matchesCondition(context: RepoContextSnapshot, condition: SkillCondition): boolean {
42
+ return condition.all.every((predicate) => factValues(context, predicate.fact).includes(predicate.includes));
43
+ }
44
+
45
+ function matchesSkillActivationConditions(context: RepoContextSnapshot, skill: SkillDefinition): boolean {
46
+ if (skill.activation_conditions.length === 0) {
47
+ return true;
48
+ }
49
+
50
+ return skill.activation_conditions.some((condition) => matchesCondition(context, condition));
51
+ }
52
+
53
+ function assertStageCompatible(skill: SkillDefinition, stage: FlowStage): void {
54
+ if (!skill.stage_compatibility.includes(stage)) {
55
+ throw new ProdifyError(`Skill "${skill.id}" is not compatible with stage "${stage}".`, {
56
+ code: 'SKILL_STAGE_INCOMPATIBLE'
57
+ });
58
+ }
59
+ }
60
+
61
+ function resolveSkill(registry: Map<string, SkillDefinition>, skillId: string, stage: FlowStage): SkillDefinition {
62
+ const skill = registry.get(skillId);
63
+ if (!skill) {
64
+ throw new ProdifyError(`Stage skill routing references unknown skill "${skillId}".`, {
65
+ code: 'SKILL_NOT_FOUND'
66
+ });
67
+ }
68
+
69
+ assertStageCompatible(skill, stage);
70
+ return skill;
71
+ }
72
+
73
+ function defaultRecord(skill: SkillDefinition, active: boolean): SkillActivationRecord {
74
+ return {
75
+ id: skill.id,
76
+ name: skill.name,
77
+ category: skill.category,
78
+ source: 'default',
79
+ active,
80
+ reason: active ? 'default stage skill' : `inactive: ${formatSkillActivationConditions(skill)}`
81
+ };
82
+ }
83
+
84
+ function conditionalRecord(skill: SkillDefinition, conditionLabel: string, active: boolean): SkillActivationRecord {
85
+ return {
86
+ id: skill.id,
87
+ name: skill.name,
88
+ category: skill.category,
89
+ source: 'conditional',
90
+ active,
91
+ reason: active ? conditionLabel : `inactive: ${conditionLabel}`
92
+ };
93
+ }
94
+
95
+ function validateRouting(routing: StageSkillRouting, registry: Map<string, SkillDefinition>, stage: FlowStage): void {
96
+ for (const skillId of routing.allowed_skills) {
97
+ resolveSkill(registry, skillId, stage);
98
+ }
99
+ }
100
+
101
+ export async function resolveStageSkills(repoRoot: string, stage: FlowStage): Promise<StageSkillResolution> {
102
+ const [contract, registry, context] = await Promise.all([
103
+ loadCompiledContract(repoRoot, stage),
104
+ loadSkillRegistry(repoRoot),
105
+ detectRepoContext(repoRoot)
106
+ ]);
107
+ const routing = contract.skill_routing;
108
+
109
+ validateRouting(routing, registry, stage);
110
+
111
+ const consideredSkills: SkillActivationRecord[] = [];
112
+ const activeSkillIds = new Set<string>();
113
+
114
+ for (const skillId of routing.default_skills) {
115
+ const skill = resolveSkill(registry, skillId, stage);
116
+ const active = matchesSkillActivationConditions(context, skill);
117
+ consideredSkills.push(defaultRecord(skill, active));
118
+ if (active) {
119
+ activeSkillIds.add(skill.id);
120
+ }
121
+ }
122
+
123
+ for (const rule of routing.conditional_skills) {
124
+ const skill = resolveSkill(registry, rule.skill, stage);
125
+ const conditionLabel = rule.reason || formatCondition(rule.when);
126
+ const active = matchesCondition(context, rule.when) && matchesSkillActivationConditions(context, skill);
127
+ const existing = consideredSkills.find((entry) => entry.id === skill.id);
128
+ if (existing) {
129
+ if (active) {
130
+ existing.active = true;
131
+ existing.reason = `${existing.reason}; ${conditionLabel}`;
132
+ activeSkillIds.add(skill.id);
133
+ } else if (!existing.active && skill.activation_conditions.length > 0) {
134
+ existing.reason = `inactive: ${formatSkillActivationConditions(skill)}`;
135
+ }
136
+ continue;
137
+ }
138
+
139
+ consideredSkills.push(conditionalRecord(skill, conditionLabel, active));
140
+ if (active) {
141
+ activeSkillIds.add(skill.id);
142
+ }
143
+ }
144
+
145
+ return {
146
+ stage,
147
+ context,
148
+ considered_skills: consideredSkills.sort((left, right) => left.id.localeCompare(right.id)),
149
+ active_skill_ids: [...activeSkillIds].sort((left, right) => left.localeCompare(right))
150
+ };
151
+ }
@@ -0,0 +1,264 @@
1
+ import fs from 'node:fs/promises';
2
+
3
+ import { ProdifyError } from './errors.js';
4
+ import { pathExists, writeFileEnsuringDir } from './fs.js';
5
+ import { isRuntimeProfileName, resolveCanonicalPath } from './paths.js';
6
+ import type {
7
+ ExecutionMode,
8
+ FlowStage,
9
+ ProdifyState,
10
+ RuntimeBootstrapMetadata,
11
+ RuntimeFailureMetadata,
12
+ RuntimeStateBlock,
13
+ RuntimeStatus,
14
+ StageValidationResult,
15
+ ValidationResult,
16
+ VersionMetadata
17
+ } from '../types.js';
18
+
19
+ export const RUNTIME_STATE_SCHEMA_VERSION = '2';
20
+
21
+ export const RUNTIME_STATUS: Record<string, RuntimeStatus> = {
22
+ NOT_BOOTSTRAPPED: 'not_bootstrapped',
23
+ READY: 'ready',
24
+ RUNNING: 'running',
25
+ AWAITING_VALIDATION: 'awaiting_validation',
26
+ BLOCKED: 'blocked',
27
+ FAILED: 'failed',
28
+ COMPLETE: 'complete'
29
+ };
30
+
31
+ function isExecutionMode(value: unknown): value is ExecutionMode {
32
+ return value === 'interactive' || value === 'auto';
33
+ }
34
+
35
+ function isFlowStage(value: unknown): value is FlowStage {
36
+ return value === 'understand'
37
+ || value === 'diagnose'
38
+ || value === 'architecture'
39
+ || value === 'plan'
40
+ || value === 'refactor'
41
+ || value === 'validate';
42
+ }
43
+
44
+ function isRuntimeStatus(value: unknown): value is RuntimeStatus {
45
+ return value === 'not_bootstrapped'
46
+ || value === 'ready'
47
+ || value === 'running'
48
+ || value === 'awaiting_validation'
49
+ || value === 'blocked'
50
+ || value === 'failed'
51
+ || value === 'complete';
52
+ }
53
+
54
+ function isValidationResult(value: unknown): value is ValidationResult {
55
+ return value === 'unknown' || value === 'pass' || value === 'fail' || value === 'inconclusive';
56
+ }
57
+
58
+ function isRuntimeContractState(value: unknown): value is RuntimeStateBlock['current_state'] {
59
+ return typeof value === 'string' && [
60
+ 'not_bootstrapped',
61
+ 'bootstrapped',
62
+ 'understand_pending',
63
+ 'understand_complete',
64
+ 'diagnose_pending',
65
+ 'diagnose_complete',
66
+ 'architecture_pending',
67
+ 'architecture_complete',
68
+ 'plan_pending',
69
+ 'plan_complete',
70
+ 'refactor_pending',
71
+ 'refactor_complete',
72
+ 'validate_pending',
73
+ 'validate_complete',
74
+ 'blocked',
75
+ 'failed',
76
+ 'completed'
77
+ ].includes(value);
78
+ }
79
+
80
+ function normalizeStageList(value: unknown): FlowStage[] {
81
+ if (!Array.isArray(value)) {
82
+ return [];
83
+ }
84
+
85
+ return value.filter(isFlowStage);
86
+ }
87
+
88
+ function asRecord(value: unknown): Record<string, unknown> {
89
+ return typeof value === 'object' && value !== null ? value as Record<string, unknown> : {};
90
+ }
91
+
92
+ function normalizeBootstrapMetadata(value: unknown): RuntimeBootstrapMetadata {
93
+ const record = asRecord(value);
94
+ return {
95
+ bootstrapped: Boolean(record.bootstrapped)
96
+ };
97
+ }
98
+
99
+ function normalizeFailureMetadata(value: unknown): RuntimeFailureMetadata | null {
100
+ if (!value) {
101
+ return null;
102
+ }
103
+
104
+ const record = asRecord(value);
105
+ return {
106
+ stage: isFlowStage(record.stage) ? record.stage : null,
107
+ contract_version: typeof record.contract_version === 'string' ? record.contract_version : null,
108
+ reason: typeof record.reason === 'string' ? record.reason : 'unknown failure'
109
+ };
110
+ }
111
+
112
+ function normalizeValidation(value: unknown): StageValidationResult | null {
113
+ if (!value) {
114
+ return null;
115
+ }
116
+
117
+ const record = asRecord(value);
118
+ return {
119
+ stage: isFlowStage(record.stage) ? record.stage : 'understand',
120
+ contract_version: typeof record.contract_version === 'string' ? record.contract_version : 'unknown',
121
+ passed: Boolean(record.passed),
122
+ violated_rules: Array.isArray(record.violated_rules)
123
+ ? record.violated_rules
124
+ .map((entry) => asRecord(entry))
125
+ .map((entry) => ({
126
+ rule: typeof entry.rule === 'string' ? entry.rule : 'unknown',
127
+ message: typeof entry.message === 'string' ? entry.message : 'validation issue',
128
+ path: typeof entry.path === 'string' ? entry.path : undefined
129
+ }))
130
+ : [],
131
+ missing_artifacts: Array.isArray(record.missing_artifacts)
132
+ ? record.missing_artifacts.filter((entry): entry is string => typeof entry === 'string')
133
+ : [],
134
+ warnings: Array.isArray(record.warnings)
135
+ ? record.warnings.filter((entry): entry is string => typeof entry === 'string')
136
+ : [],
137
+ diagnostics: Array.isArray(record.diagnostics)
138
+ ? record.diagnostics.filter((entry): entry is string => typeof entry === 'string')
139
+ : []
140
+ };
141
+ }
142
+
143
+ function normalizeValidatedContractVersions(value: unknown): Partial<Record<FlowStage, string>> {
144
+ const record = asRecord(value);
145
+ const normalized: Partial<Record<FlowStage, string>> = {};
146
+
147
+ for (const [key, rawValue] of Object.entries(record)) {
148
+ if (isFlowStage(key) && typeof rawValue === 'string') {
149
+ normalized[key] = rawValue;
150
+ }
151
+ }
152
+
153
+ return normalized;
154
+ }
155
+
156
+ export function createInitialRuntimeState({ presetMetadata }: { presetMetadata: VersionMetadata }): ProdifyState {
157
+ return {
158
+ schema_version: RUNTIME_STATE_SCHEMA_VERSION,
159
+ preset_name: presetMetadata.name,
160
+ preset_version: presetMetadata.version,
161
+ runtime: {
162
+ status: RUNTIME_STATUS.NOT_BOOTSTRAPPED,
163
+ current_state: 'not_bootstrapped',
164
+ mode: null,
165
+ current_stage: null,
166
+ current_task_id: null,
167
+ pending_stage: null,
168
+ completed_stages: [],
169
+ awaiting_user_validation: false,
170
+ last_validation_result: 'unknown',
171
+ last_validation: null,
172
+ last_validated_contract_versions: {},
173
+ resumable: false,
174
+ blocked_reason: null,
175
+ failure_metadata: null,
176
+ bootstrap: {
177
+ bootstrapped: false
178
+ },
179
+ next_action: '$prodify-init',
180
+ timestamps: {
181
+ bootstrapped_at: null,
182
+ last_transition_at: null,
183
+ completed_at: null
184
+ }
185
+ }
186
+ };
187
+ }
188
+
189
+ function normalizeRuntimeBlock(runtime: unknown): RuntimeStateBlock {
190
+ const record = asRecord(runtime);
191
+ const timestamps = asRecord(record.timestamps);
192
+
193
+ return {
194
+ status: isRuntimeStatus(record.status) ? record.status : RUNTIME_STATUS.NOT_BOOTSTRAPPED,
195
+ current_state: isRuntimeContractState(record.current_state) ? record.current_state : 'not_bootstrapped',
196
+ mode: isExecutionMode(record.mode) ? record.mode : null,
197
+ current_stage: isFlowStage(record.current_stage) ? record.current_stage : null,
198
+ current_task_id: typeof record.current_task_id === 'string' ? record.current_task_id : null,
199
+ pending_stage: isFlowStage(record.pending_stage) ? record.pending_stage : null,
200
+ completed_stages: normalizeStageList(record.completed_stages),
201
+ awaiting_user_validation: Boolean(record.awaiting_user_validation),
202
+ last_validation_result: isValidationResult(record.last_validation_result) ? record.last_validation_result : 'unknown',
203
+ last_validation: normalizeValidation(record.last_validation),
204
+ last_validated_contract_versions: normalizeValidatedContractVersions(record.last_validated_contract_versions),
205
+ resumable: Boolean(record.resumable),
206
+ blocked_reason: typeof record.blocked_reason === 'string' ? record.blocked_reason : null,
207
+ failure_metadata: normalizeFailureMetadata(record.failure_metadata),
208
+ bootstrap: normalizeBootstrapMetadata(record.bootstrap),
209
+ next_action: typeof record.next_action === 'string' ? record.next_action : '$prodify-init',
210
+ timestamps: {
211
+ bootstrapped_at: typeof timestamps.bootstrapped_at === 'string' ? timestamps.bootstrapped_at : null,
212
+ last_transition_at: typeof timestamps.last_transition_at === 'string' ? timestamps.last_transition_at : null,
213
+ completed_at: typeof timestamps.completed_at === 'string' ? timestamps.completed_at : null
214
+ }
215
+ };
216
+ }
217
+
218
+ export function normalizeRuntimeState(raw: unknown, { presetMetadata }: { presetMetadata: VersionMetadata }): ProdifyState {
219
+ const record = asRecord(raw);
220
+ const base = createInitialRuntimeState({ presetMetadata });
221
+
222
+ return {
223
+ ...base,
224
+ schema_version: base.schema_version,
225
+ preset_name: presetMetadata.name,
226
+ preset_version: presetMetadata.version,
227
+ runtime: normalizeRuntimeBlock(record.runtime)
228
+ };
229
+ }
230
+
231
+ export function serializeRuntimeState(state: ProdifyState): string {
232
+ return `${JSON.stringify(state, null, 2)}\n`;
233
+ }
234
+
235
+ export async function readRuntimeState(
236
+ repoRoot: string,
237
+ { allowMissing = false, presetMetadata }: { allowMissing?: boolean; presetMetadata: VersionMetadata }
238
+ ): Promise<ProdifyState | null> {
239
+ const statePath = resolveCanonicalPath(repoRoot, '.prodify/state.json');
240
+
241
+ if (!(await pathExists(statePath))) {
242
+ if (allowMissing) {
243
+ return null;
244
+ }
245
+
246
+ throw new ProdifyError('Runtime state is missing: .prodify/state.json', {
247
+ code: 'RUNTIME_STATE_MISSING'
248
+ });
249
+ }
250
+
251
+ try {
252
+ const raw = JSON.parse(await fs.readFile(statePath, 'utf8'));
253
+ return normalizeRuntimeState(raw, { presetMetadata });
254
+ } catch {
255
+ throw new ProdifyError('Runtime state is malformed: .prodify/state.json', {
256
+ code: 'RUNTIME_STATE_MALFORMED'
257
+ });
258
+ }
259
+ }
260
+
261
+ export async function writeRuntimeState(repoRoot: string, state: ProdifyState): Promise<void> {
262
+ const statePath = resolveCanonicalPath(repoRoot, '.prodify/state.json');
263
+ await writeFileEnsuringDir(statePath, serializeRuntimeState(state));
264
+ }