@topogram/cli 0.3.34

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 (257) hide show
  1. package/ARCHITECTURE.md +67 -0
  2. package/CHANGELOG.md +240 -0
  3. package/README.md +223 -0
  4. package/package.json +51 -0
  5. package/src/adoption/index.js +3 -0
  6. package/src/adoption/plan.js +702 -0
  7. package/src/adoption/reporting.js +464 -0
  8. package/src/adoption/review-groups.js +313 -0
  9. package/src/agent-ops/query-builders.js +5012 -0
  10. package/src/archive/archive.js +141 -0
  11. package/src/archive/compact.js +26 -0
  12. package/src/archive/jsonl.js +70 -0
  13. package/src/archive/resolver-bridge.js +82 -0
  14. package/src/archive/schema.js +87 -0
  15. package/src/archive/unarchive.js +108 -0
  16. package/src/catalog.js +752 -0
  17. package/src/cli/catalog-alias.js +166 -0
  18. package/src/cli.js +9738 -0
  19. package/src/component-behavior.js +173 -0
  20. package/src/example-implementation.js +91 -0
  21. package/src/format.js +19 -0
  22. package/src/generator/adapters.d.ts +4 -0
  23. package/src/generator/adapters.js +325 -0
  24. package/src/generator/api.d.ts +1 -0
  25. package/src/generator/api.js +1196 -0
  26. package/src/generator/check.js +355 -0
  27. package/src/generator/component-conformance.js +767 -0
  28. package/src/generator/components.js +39 -0
  29. package/src/generator/context/bundle.js +291 -0
  30. package/src/generator/context/diff.js +256 -0
  31. package/src/generator/context/digest.js +182 -0
  32. package/src/generator/context/domain-coverage.js +94 -0
  33. package/src/generator/context/domain-page.js +137 -0
  34. package/src/generator/context/index.js +42 -0
  35. package/src/generator/context/report.js +121 -0
  36. package/src/generator/context/shared.js +1397 -0
  37. package/src/generator/context/slice.js +703 -0
  38. package/src/generator/context/task-mode.js +466 -0
  39. package/src/generator/docs.js +327 -0
  40. package/src/generator/index.js +161 -0
  41. package/src/generator/native/parity-bundle.js +311 -0
  42. package/src/generator/output.js +300 -0
  43. package/src/generator/registry.js +482 -0
  44. package/src/generator/runtime/app-bundle.js +456 -0
  45. package/src/generator/runtime/bundle-shared.js +166 -0
  46. package/src/generator/runtime/compile-check.js +163 -0
  47. package/src/generator/runtime/deployment.js +287 -0
  48. package/src/generator/runtime/environment.js +635 -0
  49. package/src/generator/runtime/index.js +32 -0
  50. package/src/generator/runtime/runtime-check.js +554 -0
  51. package/src/generator/runtime/shared.js +515 -0
  52. package/src/generator/runtime/smoke.js +219 -0
  53. package/src/generator/schema.js +204 -0
  54. package/src/generator/sdlc/board.js +66 -0
  55. package/src/generator/sdlc/doc-page.js +53 -0
  56. package/src/generator/sdlc/index.js +23 -0
  57. package/src/generator/sdlc/release-notes.js +62 -0
  58. package/src/generator/sdlc/traceability-matrix.js +65 -0
  59. package/src/generator/shared.js +29 -0
  60. package/src/generator/surfaces/contracts.js +146 -0
  61. package/src/generator/surfaces/databases/contract.js +40 -0
  62. package/src/generator/surfaces/databases/index.js +84 -0
  63. package/src/generator/surfaces/databases/lifecycle-shared.d.ts +1 -0
  64. package/src/generator/surfaces/databases/lifecycle-shared.js +612 -0
  65. package/src/generator/surfaces/databases/migration-plan.js +281 -0
  66. package/src/generator/surfaces/databases/postgres/capabilities.js +14 -0
  67. package/src/generator/surfaces/databases/postgres/drizzle.js +99 -0
  68. package/src/generator/surfaces/databases/postgres/index.js +9 -0
  69. package/src/generator/surfaces/databases/postgres/lifecycle.js +16 -0
  70. package/src/generator/surfaces/databases/postgres/prisma.js +159 -0
  71. package/src/generator/surfaces/databases/postgres/sql-migration.js +102 -0
  72. package/src/generator/surfaces/databases/postgres/sql-schema.js +34 -0
  73. package/src/generator/surfaces/databases/shared.d.ts +1 -0
  74. package/src/generator/surfaces/databases/shared.js +350 -0
  75. package/src/generator/surfaces/databases/snapshot.js +96 -0
  76. package/src/generator/surfaces/databases/sqlite/capabilities.js +14 -0
  77. package/src/generator/surfaces/databases/sqlite/index.js +8 -0
  78. package/src/generator/surfaces/databases/sqlite/lifecycle.js +16 -0
  79. package/src/generator/surfaces/databases/sqlite/prisma.js +143 -0
  80. package/src/generator/surfaces/databases/sqlite/sql-migration.js +65 -0
  81. package/src/generator/surfaces/databases/sqlite/sql-schema.js +27 -0
  82. package/src/generator/surfaces/index.js +25 -0
  83. package/src/generator/surfaces/native/swiftui-app.js +38 -0
  84. package/src/generator/surfaces/native/swiftui-templates/Package.swift.txt +20 -0
  85. package/src/generator/surfaces/native/swiftui-templates/README.generated.md +26 -0
  86. package/src/generator/surfaces/native/swiftui-templates/runtime/DynamicScreens.swift +682 -0
  87. package/src/generator/surfaces/native/swiftui-templates/runtime/TodoAPIClient.swift +156 -0
  88. package/src/generator/surfaces/native/swiftui-templates/runtime/TodoSwiftUIApp.swift +44 -0
  89. package/src/generator/surfaces/native/swiftui-templates/runtime/Visibility.swift +183 -0
  90. package/src/generator/surfaces/services/express.d.ts +1 -0
  91. package/src/generator/surfaces/services/express.js +766 -0
  92. package/src/generator/surfaces/services/hono.d.ts +1 -0
  93. package/src/generator/surfaces/services/hono.js +204 -0
  94. package/src/generator/surfaces/services/index.js +42 -0
  95. package/src/generator/surfaces/services/persistence-wiring.js +240 -0
  96. package/src/generator/surfaces/services/runtime-helpers.js +631 -0
  97. package/src/generator/surfaces/services/server-contract.js +80 -0
  98. package/src/generator/surfaces/services/stateless.d.ts +1 -0
  99. package/src/generator/surfaces/services/stateless.js +97 -0
  100. package/src/generator/surfaces/shared.js +64 -0
  101. package/src/generator/surfaces/web/api-client.js +1 -0
  102. package/src/generator/surfaces/web/forms.js +1 -0
  103. package/src/generator/surfaces/web/index.d.ts +2 -0
  104. package/src/generator/surfaces/web/index.js +53 -0
  105. package/src/generator/surfaces/web/react-components.js +248 -0
  106. package/src/generator/surfaces/web/react.js +538 -0
  107. package/src/generator/surfaces/web/routes.js +1 -0
  108. package/src/generator/surfaces/web/screens.js +1 -0
  109. package/src/generator/surfaces/web/shared.js +369 -0
  110. package/src/generator/surfaces/web/sveltekit-actions.js +28 -0
  111. package/src/generator/surfaces/web/sveltekit-components.js +234 -0
  112. package/src/generator/surfaces/web/sveltekit.js +426 -0
  113. package/src/generator/surfaces/web/ui-web-contract.js +65 -0
  114. package/src/generator/surfaces/web/vanilla.js +239 -0
  115. package/src/generator/verification.js +84 -0
  116. package/src/generator.js +1 -0
  117. package/src/import/core/context.js +52 -0
  118. package/src/import/core/contracts.js +23 -0
  119. package/src/import/core/registry.js +81 -0
  120. package/src/import/core/runner.js +646 -0
  121. package/src/import/core/shared.js +910 -0
  122. package/src/import/enrichers/auth-session.js +18 -0
  123. package/src/import/enrichers/django-rest.js +226 -0
  124. package/src/import/enrichers/doc-linking.js +20 -0
  125. package/src/import/enrichers/rails-controllers.js +246 -0
  126. package/src/import/enrichers/rails-models.js +130 -0
  127. package/src/import/enrichers/workflow-target-state.js +10 -0
  128. package/src/import/extractors/api/aspnet-core.js +304 -0
  129. package/src/import/extractors/api/django-routes.js +318 -0
  130. package/src/import/extractors/api/express.js +154 -0
  131. package/src/import/extractors/api/fastify.js +371 -0
  132. package/src/import/extractors/api/flutter-dio.js +135 -0
  133. package/src/import/extractors/api/generic-route-fallback.js +90 -0
  134. package/src/import/extractors/api/graphql-code-first.js +565 -0
  135. package/src/import/extractors/api/graphql-sdl.js +309 -0
  136. package/src/import/extractors/api/jaxrs.js +303 -0
  137. package/src/import/extractors/api/micronaut.js +213 -0
  138. package/src/import/extractors/api/next-route.js +50 -0
  139. package/src/import/extractors/api/next-server-action.js +51 -0
  140. package/src/import/extractors/api/nextauth.js +52 -0
  141. package/src/import/extractors/api/openapi-code.js +242 -0
  142. package/src/import/extractors/api/openapi.js +232 -0
  143. package/src/import/extractors/api/rails-routes.js +230 -0
  144. package/src/import/extractors/api/react-native-repository.js +128 -0
  145. package/src/import/extractors/api/retrofit.js +103 -0
  146. package/src/import/extractors/api/spring-web.js +372 -0
  147. package/src/import/extractors/api/swift-webapi.js +116 -0
  148. package/src/import/extractors/api/trpc.js +212 -0
  149. package/src/import/extractors/db/django-models.js +232 -0
  150. package/src/import/extractors/db/dotnet-models.js +93 -0
  151. package/src/import/extractors/db/drizzle.js +242 -0
  152. package/src/import/extractors/db/ef-core.js +221 -0
  153. package/src/import/extractors/db/flutter-entities.js +120 -0
  154. package/src/import/extractors/db/jpa.js +120 -0
  155. package/src/import/extractors/db/liquibase.js +180 -0
  156. package/src/import/extractors/db/mybatis-xml.js +145 -0
  157. package/src/import/extractors/db/prisma.js +185 -0
  158. package/src/import/extractors/db/rails-schema.js +175 -0
  159. package/src/import/extractors/db/react-native-entities.js +95 -0
  160. package/src/import/extractors/db/room.js +193 -0
  161. package/src/import/extractors/db/snapshot.js +112 -0
  162. package/src/import/extractors/db/sql.js +180 -0
  163. package/src/import/extractors/db/swiftdata.js +137 -0
  164. package/src/import/extractors/ui/android-compose.js +230 -0
  165. package/src/import/extractors/ui/backend-only.js +70 -0
  166. package/src/import/extractors/ui/blazor.js +227 -0
  167. package/src/import/extractors/ui/flutter-screens.js +152 -0
  168. package/src/import/extractors/ui/maui-xaml.js +135 -0
  169. package/src/import/extractors/ui/next-app-router.js +83 -0
  170. package/src/import/extractors/ui/next-pages-router.js +141 -0
  171. package/src/import/extractors/ui/razor-pages.js +181 -0
  172. package/src/import/extractors/ui/react-native-screens.js +166 -0
  173. package/src/import/extractors/ui/react-router.js +139 -0
  174. package/src/import/extractors/ui/sveltekit.js +123 -0
  175. package/src/import/extractors/ui/swiftui.js +193 -0
  176. package/src/import/extractors/ui/uikit.js +175 -0
  177. package/src/import/extractors/verification/generic.js +290 -0
  178. package/src/import/extractors/workflows/generic.js +137 -0
  179. package/src/import/index.js +7 -0
  180. package/src/import/provenance.js +158 -0
  181. package/src/new-project.js +2107 -0
  182. package/src/parser.js +439 -0
  183. package/src/policy/review-boundaries.js +165 -0
  184. package/src/project-config.js +535 -0
  185. package/src/proofs/backend-parity.js +19 -0
  186. package/src/proofs/contract-audit.js +220 -0
  187. package/src/proofs/ios-parity.js +7 -0
  188. package/src/proofs/issues-parity.js +10 -0
  189. package/src/proofs/web-parity.js +50 -0
  190. package/src/realization/api/build-api-realization.js +5 -0
  191. package/src/realization/api/index.js +1 -0
  192. package/src/realization/backend/build-backend-runtime-realization.js +82 -0
  193. package/src/realization/backend/index.d.ts +1 -0
  194. package/src/realization/backend/index.js +4 -0
  195. package/src/realization/db/build-db-realization.js +17 -0
  196. package/src/realization/db/index.js +3 -0
  197. package/src/realization/db/migration-plan.js +5 -0
  198. package/src/realization/db/snapshot.js +5 -0
  199. package/src/realization/ui/build-ui-shared-realization.js +305 -0
  200. package/src/realization/ui/build-web-realization.js +189 -0
  201. package/src/realization/ui/index.js +2 -0
  202. package/src/reconcile/docs.js +280 -0
  203. package/src/reconcile/index.js +3 -0
  204. package/src/reconcile/journeys.js +441 -0
  205. package/src/resolver/docs.js +1 -0
  206. package/src/resolver/enrich/acceptance-criterion.js +14 -0
  207. package/src/resolver/enrich/bug.js +12 -0
  208. package/src/resolver/enrich/component.js +2 -0
  209. package/src/resolver/enrich/index.js +1 -0
  210. package/src/resolver/enrich/pitch.js +18 -0
  211. package/src/resolver/enrich/requirement.js +20 -0
  212. package/src/resolver/enrich/task.js +16 -0
  213. package/src/resolver/expressions.js +1 -0
  214. package/src/resolver/index.js +2422 -0
  215. package/src/resolver/normalize.js +1 -0
  216. package/src/resolver.js +1 -0
  217. package/src/sdlc/adopt.js +65 -0
  218. package/src/sdlc/check.js +86 -0
  219. package/src/sdlc/dod/acceptance-criterion.js +22 -0
  220. package/src/sdlc/dod/bug.js +26 -0
  221. package/src/sdlc/dod/document.js +23 -0
  222. package/src/sdlc/dod/index.js +25 -0
  223. package/src/sdlc/dod/pitch.js +23 -0
  224. package/src/sdlc/dod/requirement.js +34 -0
  225. package/src/sdlc/dod/task.js +39 -0
  226. package/src/sdlc/explain.js +116 -0
  227. package/src/sdlc/history.js +80 -0
  228. package/src/sdlc/paths.js +11 -0
  229. package/src/sdlc/release.js +106 -0
  230. package/src/sdlc/scaffold.js +89 -0
  231. package/src/sdlc/status-filter.js +54 -0
  232. package/src/sdlc/transition.js +112 -0
  233. package/src/sdlc/transitions/acceptance-criterion.js +28 -0
  234. package/src/sdlc/transitions/bug.js +31 -0
  235. package/src/sdlc/transitions/document.js +29 -0
  236. package/src/sdlc/transitions/index.js +56 -0
  237. package/src/sdlc/transitions/pitch.js +34 -0
  238. package/src/sdlc/transitions/requirement.js +31 -0
  239. package/src/sdlc/transitions/task.js +34 -0
  240. package/src/template-trust.js +597 -0
  241. package/src/validator/expressions.js +1 -0
  242. package/src/validator/index.js +3424 -0
  243. package/src/validator/kinds.js +346 -0
  244. package/src/validator/per-kind/acceptance-criterion.js +91 -0
  245. package/src/validator/per-kind/bug.js +77 -0
  246. package/src/validator/per-kind/component.js +274 -0
  247. package/src/validator/per-kind/domain.js +205 -0
  248. package/src/validator/per-kind/pitch.js +101 -0
  249. package/src/validator/per-kind/requirement.js +75 -0
  250. package/src/validator/per-kind/task.js +96 -0
  251. package/src/validator/registry.js +1 -0
  252. package/src/validator/utils.js +12 -0
  253. package/src/validator.js +1 -0
  254. package/src/workflows.js +7597 -0
  255. package/src/workspace-docs.js +265 -0
  256. package/template-helpers/react.js +5 -0
  257. package/template-helpers/sveltekit.js +5 -0
@@ -0,0 +1,89 @@
1
+ // `sdlc new <kind> <slug>` — scaffold a new SDLC `.tg` file with sensible
2
+ // defaults so the author can fill in details rather than start blank.
3
+
4
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
5
+ import path from "node:path";
6
+
7
+ const TEMPLATES = {
8
+ pitch: (slug) => `pitch pitch_${slug} {
9
+ name "${humanize(slug)}"
10
+ description "Why this work matters."
11
+ appetite "TBD — small / medium / large"
12
+ problem "Describe the user-visible problem."
13
+ solution_sketch "Describe the proposed shape, not the implementation."
14
+ rabbit_holes "Known traps to avoid."
15
+ no_go_areas "Things this pitch deliberately won't touch."
16
+ affects []
17
+ priority medium
18
+ status draft
19
+ }
20
+ `,
21
+ requirement: (slug) => `requirement req_${slug} {
22
+ name "${humanize(slug)}"
23
+ description "What the system must do."
24
+ affects []
25
+ introduces_rules []
26
+ respects_rules []
27
+ priority medium
28
+ status draft
29
+ }
30
+ `,
31
+ acceptance_criterion: (slug) => `acceptance_criterion ac_${slug} {
32
+ name "${humanize(slug)}"
33
+ description "Given <state>, when <action>, then <observable>."
34
+ requirement req_TODO
35
+ status draft
36
+ }
37
+ `,
38
+ task: (slug) => `task task_${slug} {
39
+ name "${humanize(slug)}"
40
+ description "What the agent or human will do."
41
+ satisfies []
42
+ acceptance_refs []
43
+ affects []
44
+ blocked_by []
45
+ priority medium
46
+ work_type implementation
47
+ status unclaimed
48
+ }
49
+ `,
50
+ bug: (slug) => `bug bug_${slug} {
51
+ name "${humanize(slug)}"
52
+ description "What goes wrong."
53
+ reproduction "Steps to reproduce."
54
+ affects []
55
+ violates []
56
+ priority medium
57
+ severity medium
58
+ status open
59
+ }
60
+ `
61
+ };
62
+
63
+ function humanize(slug) {
64
+ return slug
65
+ .replace(/[_-]+/g, " ")
66
+ .replace(/\b\w/g, (m) => m.toUpperCase());
67
+ }
68
+
69
+ export function scaffoldNew(workspaceRoot, kind, slug) {
70
+ if (!TEMPLATES[kind]) {
71
+ return { ok: false, error: `Unsupported kind '${kind}' (allowed: ${Object.keys(TEMPLATES).join(", ")})` };
72
+ }
73
+ if (!slug || !/^[a-z][a-z0-9_]*$/.test(slug)) {
74
+ return { ok: false, error: `Invalid slug '${slug}' — must match /^[a-z][a-z0-9_]*$/` };
75
+ }
76
+ const targetDir = path.join(workspaceRoot, "topogram", `${kind === "acceptance_criterion" ? "acceptance_criteria" : kind + "s"}`);
77
+ if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
78
+ const targetFile = path.join(targetDir, `${slug}.tg`);
79
+ if (existsSync(targetFile)) {
80
+ return { ok: false, error: `Refusing to overwrite '${targetFile}'` };
81
+ }
82
+ writeFileSync(targetFile, TEMPLATES[kind](slug), "utf8");
83
+ return {
84
+ ok: true,
85
+ kind,
86
+ slug,
87
+ file: targetFile
88
+ };
89
+ }
@@ -0,0 +1,54 @@
1
+ // Default-active status filtering for SDLC queries.
2
+ //
3
+ // Most CLI surfaces (board, slice, traceability) want to omit terminal
4
+ // statuses unless the caller passes `--include-status` or
5
+ // `--include-archived`. This module centralizes the rules.
6
+
7
+ import {
8
+ isArchivableStatus,
9
+ isTerminalStatus
10
+ } from "./transitions/index.js";
11
+
12
+ const ALWAYS_VISIBLE_TERMINAL_STATUSES = new Set([
13
+ "approved",
14
+ "superseded",
15
+ "verified",
16
+ "wont-fix",
17
+ "done"
18
+ ]);
19
+
20
+ export function defaultActiveStatuses(kind) {
21
+ // For a given kind, the statuses that show up in queries by default.
22
+ // Terminal statuses are still visible if they aren't archive-eligible
23
+ // (e.g. requirement.superseded is terminal but stays for traceability).
24
+ switch (kind) {
25
+ case "pitch":
26
+ return new Set(["draft", "shaped", "submitted", "approved"]);
27
+ case "requirement":
28
+ return new Set(["draft", "in-review", "approved", "superseded"]);
29
+ case "acceptance_criterion":
30
+ return new Set(["draft", "approved", "superseded"]);
31
+ case "task":
32
+ return new Set(["unclaimed", "claimed", "in-progress", "blocked"]);
33
+ case "bug":
34
+ return new Set(["open", "in-progress", "fixed"]);
35
+ case "document":
36
+ return new Set(["draft", "review", "published"]);
37
+ default:
38
+ return null;
39
+ }
40
+ }
41
+
42
+ export function filterStatements(statements, options = {}) {
43
+ const includeArchived = options.includeArchived === true;
44
+ const includeStatuses = options.includeStatuses ? new Set(options.includeStatuses) : null;
45
+ return statements.filter((s) => {
46
+ if (s.archived && !includeArchived) return false;
47
+ const defaults = defaultActiveStatuses(s.kind);
48
+ if (!defaults) return true;
49
+ if (includeStatuses && includeStatuses.has(s.status)) return true;
50
+ return defaults.has(s.status);
51
+ });
52
+ }
53
+
54
+ export { isTerminalStatus, isArchivableStatus, ALWAYS_VISIBLE_TERMINAL_STATUSES };
@@ -0,0 +1,112 @@
1
+ // SDLC transition orchestrator.
2
+ //
3
+ // `transitionStatement(workspace, id, targetStatus, options)`:
4
+ // 1. Locate the statement in the resolved graph.
5
+ // 2. Validate the transition with the kind's state machine.
6
+ // 3. Run DoD (Definition of Done) checks. Errors block; warnings advise.
7
+ // 4. Surgically rewrite the `status` value in the source `.tg` file.
8
+ // 5. Append a history record to `.topogram-sdlc-history.json`.
9
+ //
10
+ // The rewrite is byte-precise — we only swap the status symbol token, so
11
+ // formatting, comments, and adjacent fields are untouched.
12
+
13
+ import { readFileSync, writeFileSync } from "node:fs";
14
+
15
+ import { parsePath } from "../parser.js";
16
+ import { resolveWorkspace } from "../resolver/index.js";
17
+ import { validateTransition, isArchivableStatus } from "./transitions/index.js";
18
+ import { checkDoD } from "./dod/index.js";
19
+ import { appendTransition } from "./history.js";
20
+
21
+ function findStatementInAst(workspaceAst, id) {
22
+ for (const file of workspaceAst.files) {
23
+ for (const statement of file.statements) {
24
+ if (statement.id === id) {
25
+ return statement;
26
+ }
27
+ }
28
+ }
29
+ return null;
30
+ }
31
+
32
+ function findStatusField(statement) {
33
+ for (const field of statement.fields) {
34
+ if (field.key === "status") return field;
35
+ }
36
+ return null;
37
+ }
38
+
39
+ function rewriteStatusInSource(source, statusField, newStatus) {
40
+ const start = statusField.value.loc.start.offset;
41
+ const end = statusField.value.loc.end.offset;
42
+ return source.slice(0, start) + newStatus + source.slice(end);
43
+ }
44
+
45
+ export function transitionStatement(workspaceRoot, id, targetStatus, options = {}) {
46
+ const ast = parsePath(workspaceRoot);
47
+ const resolved = resolveWorkspace(ast);
48
+ if (!resolved.ok) {
49
+ return {
50
+ ok: false,
51
+ error: "workspace failed validation; cannot transition",
52
+ validation: resolved.validation
53
+ };
54
+ }
55
+
56
+ const astStatement = findStatementInAst(ast, id);
57
+ if (!astStatement) {
58
+ return { ok: false, error: `Statement '${id}' not found` };
59
+ }
60
+
61
+ const resolvedStatement = resolved.graph.statements.find((s) => s.id === id);
62
+ if (!resolvedStatement) {
63
+ return { ok: false, error: `Statement '${id}' not found in resolved graph` };
64
+ }
65
+
66
+ const fromStatus = resolvedStatement.status;
67
+ const transition = validateTransition(astStatement.kind, fromStatus, targetStatus);
68
+ if (!transition.ok) {
69
+ return { ok: false, error: transition.error };
70
+ }
71
+
72
+ const byId = new Map(resolved.graph.statements.map((s) => [s.id, s]));
73
+ const dod = checkDoD(astStatement.kind, resolvedStatement, targetStatus, { byId });
74
+ if (!dod.satisfied && !options.force) {
75
+ return {
76
+ ok: false,
77
+ error: `Definition of Done not satisfied: ${dod.errors.join("; ")}`,
78
+ dod
79
+ };
80
+ }
81
+
82
+ const statusField = findStatusField(astStatement);
83
+ if (!statusField) {
84
+ return { ok: false, error: `Statement '${id}' has no status field to rewrite` };
85
+ }
86
+
87
+ const sourcePath = astStatement.loc.file;
88
+ const original = readFileSync(sourcePath, "utf8");
89
+ const rewritten = rewriteStatusInSource(original, statusField, targetStatus);
90
+
91
+ if (!options.dryRun) {
92
+ writeFileSync(sourcePath, rewritten, "utf8");
93
+ appendTransition(workspaceRoot, id, {
94
+ from: fromStatus,
95
+ to: targetStatus,
96
+ by: options.actor || null,
97
+ note: options.note || null
98
+ });
99
+ }
100
+
101
+ return {
102
+ ok: true,
103
+ id,
104
+ kind: astStatement.kind,
105
+ from: fromStatus,
106
+ to: targetStatus,
107
+ file: sourcePath,
108
+ archivable: isArchivableStatus(astStatement.kind, targetStatus),
109
+ dod,
110
+ dryRun: options.dryRun === true
111
+ };
112
+ }
@@ -0,0 +1,28 @@
1
+ // Acceptance criterion state machine.
2
+ //
3
+ // draft → approved → superseded
4
+ //
5
+ // ACs are always-live like requirements.
6
+
7
+ export const LEGAL_TRANSITIONS = {
8
+ draft: ["approved"],
9
+ approved: ["superseded", "draft"],
10
+ superseded: []
11
+ };
12
+
13
+ export const TERMINAL_STATUSES = new Set(["superseded"]);
14
+ export const ARCHIVABLE_STATUSES = new Set();
15
+
16
+ export function validateTransition(from, to) {
17
+ const allowed = LEGAL_TRANSITIONS[from];
18
+ if (!allowed) {
19
+ return { ok: false, error: `Unknown acceptance_criterion status '${from}'` };
20
+ }
21
+ if (!allowed.includes(to)) {
22
+ return {
23
+ ok: false,
24
+ error: `Acceptance criterion cannot transition from '${from}' to '${to}' — allowed: ${allowed.join(", ") || "(terminal)"}`
25
+ };
26
+ }
27
+ return { ok: true };
28
+ }
@@ -0,0 +1,31 @@
1
+ // Bug state machine.
2
+ //
3
+ // open → in-progress → fixed → verified
4
+ // └──────────┴────────┴────→ wont-fix
5
+ //
6
+ // `verified` and `wont-fix` are archive-eligible.
7
+
8
+ export const LEGAL_TRANSITIONS = {
9
+ open: ["in-progress", "wont-fix"],
10
+ "in-progress": ["fixed", "open", "wont-fix"],
11
+ fixed: ["verified", "in-progress", "wont-fix"],
12
+ verified: ["in-progress"],
13
+ "wont-fix": ["open"]
14
+ };
15
+
16
+ export const TERMINAL_STATUSES = new Set(["verified", "wont-fix"]);
17
+ export const ARCHIVABLE_STATUSES = new Set(["verified", "wont-fix"]);
18
+
19
+ export function validateTransition(from, to) {
20
+ const allowed = LEGAL_TRANSITIONS[from];
21
+ if (!allowed) {
22
+ return { ok: false, error: `Unknown bug status '${from}'` };
23
+ }
24
+ if (!allowed.includes(to)) {
25
+ return {
26
+ ok: false,
27
+ error: `Bug cannot transition from '${from}' to '${to}' — allowed: ${allowed.join(", ")}`
28
+ };
29
+ }
30
+ return { ok: true };
31
+ }
@@ -0,0 +1,29 @@
1
+ // Document state machine (mirrors workspace-docs.DOC_STATUSES extension).
2
+ //
3
+ // draft → review → published → archived
4
+ // ↓
5
+ // review (when linked component changes — staleness signal)
6
+
7
+ export const LEGAL_TRANSITIONS = {
8
+ draft: ["review"],
9
+ review: ["published", "draft"],
10
+ published: ["review", "archived"],
11
+ archived: ["draft"]
12
+ };
13
+
14
+ export const TERMINAL_STATUSES = new Set(["archived"]);
15
+ export const ARCHIVABLE_STATUSES = new Set(["archived"]);
16
+
17
+ export function validateTransition(from, to) {
18
+ const allowed = LEGAL_TRANSITIONS[from];
19
+ if (!allowed) {
20
+ return { ok: false, error: `Unknown document status '${from}'` };
21
+ }
22
+ if (!allowed.includes(to)) {
23
+ return {
24
+ ok: false,
25
+ error: `Document cannot transition from '${from}' to '${to}' — allowed: ${allowed.join(", ")}`
26
+ };
27
+ }
28
+ return { ok: true };
29
+ }
@@ -0,0 +1,56 @@
1
+ // Per-kind dispatch for SDLC transitions.
2
+
3
+ import * as pitch from "./pitch.js";
4
+ import * as requirement from "./requirement.js";
5
+ import * as acceptanceCriterion from "./acceptance-criterion.js";
6
+ import * as task from "./task.js";
7
+ import * as bug from "./bug.js";
8
+ import * as document from "./document.js";
9
+
10
+ const MODULES = {
11
+ pitch,
12
+ requirement,
13
+ acceptance_criterion: acceptanceCriterion,
14
+ task,
15
+ bug,
16
+ document
17
+ };
18
+
19
+ export function getTransitionModule(kind) {
20
+ return MODULES[kind] || null;
21
+ }
22
+
23
+ export function legalTransitionsFor(kind, from) {
24
+ const mod = MODULES[kind];
25
+ if (!mod) return [];
26
+ return mod.LEGAL_TRANSITIONS[from] || [];
27
+ }
28
+
29
+ export function isTerminalStatus(kind, status) {
30
+ const mod = MODULES[kind];
31
+ if (!mod) return false;
32
+ return mod.TERMINAL_STATUSES.has(status);
33
+ }
34
+
35
+ export function isArchivableStatus(kind, status) {
36
+ const mod = MODULES[kind];
37
+ if (!mod) return false;
38
+ return mod.ARCHIVABLE_STATUSES.has(status);
39
+ }
40
+
41
+ export function validateTransition(kind, from, to) {
42
+ const mod = MODULES[kind];
43
+ if (!mod) {
44
+ return { ok: false, error: `No state machine for kind '${kind}'` };
45
+ }
46
+ return mod.validateTransition(from, to);
47
+ }
48
+
49
+ export {
50
+ pitch,
51
+ requirement,
52
+ acceptanceCriterion,
53
+ task,
54
+ bug,
55
+ document
56
+ };
@@ -0,0 +1,34 @@
1
+ // Pitch state machine.
2
+ //
3
+ // draft → shaped → submitted → approved
4
+ // ↓ ↓ ↓ ↓
5
+ // └────────┴─────────┴───→ rejected
6
+ //
7
+ // A pitch may skip `shaped` and go straight to `submitted` for small or
8
+ // obvious work (per the SDLC design). All terminal-status pitches stay in
9
+ // the active workspace except `rejected`, which is archive-eligible.
10
+
11
+ export const LEGAL_TRANSITIONS = {
12
+ draft: ["shaped", "submitted", "rejected"],
13
+ shaped: ["submitted", "rejected", "draft"],
14
+ submitted: ["approved", "rejected", "shaped"],
15
+ approved: ["rejected"],
16
+ rejected: ["draft"]
17
+ };
18
+
19
+ export const TERMINAL_STATUSES = new Set(["approved", "rejected"]);
20
+ export const ARCHIVABLE_STATUSES = new Set(["rejected"]);
21
+
22
+ export function validateTransition(from, to) {
23
+ const allowed = LEGAL_TRANSITIONS[from];
24
+ if (!allowed) {
25
+ return { ok: false, error: `Unknown pitch status '${from}'` };
26
+ }
27
+ if (!allowed.includes(to)) {
28
+ return {
29
+ ok: false,
30
+ error: `Pitch cannot transition from '${from}' to '${to}' — allowed: ${allowed.join(", ")}`
31
+ };
32
+ }
33
+ return { ok: true };
34
+ }
@@ -0,0 +1,31 @@
1
+ // Requirement state machine.
2
+ //
3
+ // draft → in-review → approved → superseded
4
+ //
5
+ // Requirements are always-live: even `superseded` requirements stay in the
6
+ // active workspace because tasks may still reference them in the
7
+ // traceability matrix.
8
+
9
+ export const LEGAL_TRANSITIONS = {
10
+ draft: ["in-review"],
11
+ "in-review": ["approved", "draft"],
12
+ approved: ["superseded", "in-review"],
13
+ superseded: []
14
+ };
15
+
16
+ export const TERMINAL_STATUSES = new Set(["superseded"]);
17
+ export const ARCHIVABLE_STATUSES = new Set();
18
+
19
+ export function validateTransition(from, to) {
20
+ const allowed = LEGAL_TRANSITIONS[from];
21
+ if (!allowed) {
22
+ return { ok: false, error: `Unknown requirement status '${from}'` };
23
+ }
24
+ if (!allowed.includes(to)) {
25
+ return {
26
+ ok: false,
27
+ error: `Requirement cannot transition from '${from}' to '${to}' — allowed: ${allowed.join(", ") || "(terminal)"}`
28
+ };
29
+ }
30
+ return { ok: true };
31
+ }
@@ -0,0 +1,34 @@
1
+ // Task state machine.
2
+ //
3
+ // unclaimed → claimed → in-progress → done
4
+ // ↓ ↓
5
+ // └────→ blocked ←──┘
6
+ //
7
+ // `blocked` is exit-able back to whichever in-flight status the task came
8
+ // from; in practice agents return through `in-progress`. `done` is
9
+ // archive-eligible.
10
+
11
+ export const LEGAL_TRANSITIONS = {
12
+ unclaimed: ["claimed"],
13
+ claimed: ["in-progress", "unclaimed", "blocked"],
14
+ "in-progress": ["done", "blocked", "claimed"],
15
+ blocked: ["claimed", "in-progress"],
16
+ done: []
17
+ };
18
+
19
+ export const TERMINAL_STATUSES = new Set(["done"]);
20
+ export const ARCHIVABLE_STATUSES = new Set(["done"]);
21
+
22
+ export function validateTransition(from, to) {
23
+ const allowed = LEGAL_TRANSITIONS[from];
24
+ if (!allowed) {
25
+ return { ok: false, error: `Unknown task status '${from}'` };
26
+ }
27
+ if (!allowed.includes(to)) {
28
+ return {
29
+ ok: false,
30
+ error: `Task cannot transition from '${from}' to '${to}' — allowed: ${allowed.join(", ") || "(terminal)"}`
31
+ };
32
+ }
33
+ return { ok: true };
34
+ }