@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.
- package/ARCHITECTURE.md +67 -0
- package/CHANGELOG.md +240 -0
- package/README.md +223 -0
- package/package.json +51 -0
- package/src/adoption/index.js +3 -0
- package/src/adoption/plan.js +702 -0
- package/src/adoption/reporting.js +464 -0
- package/src/adoption/review-groups.js +313 -0
- package/src/agent-ops/query-builders.js +5012 -0
- package/src/archive/archive.js +141 -0
- package/src/archive/compact.js +26 -0
- package/src/archive/jsonl.js +70 -0
- package/src/archive/resolver-bridge.js +82 -0
- package/src/archive/schema.js +87 -0
- package/src/archive/unarchive.js +108 -0
- package/src/catalog.js +752 -0
- package/src/cli/catalog-alias.js +166 -0
- package/src/cli.js +9738 -0
- package/src/component-behavior.js +173 -0
- package/src/example-implementation.js +91 -0
- package/src/format.js +19 -0
- package/src/generator/adapters.d.ts +4 -0
- package/src/generator/adapters.js +325 -0
- package/src/generator/api.d.ts +1 -0
- package/src/generator/api.js +1196 -0
- package/src/generator/check.js +355 -0
- package/src/generator/component-conformance.js +767 -0
- package/src/generator/components.js +39 -0
- package/src/generator/context/bundle.js +291 -0
- package/src/generator/context/diff.js +256 -0
- package/src/generator/context/digest.js +182 -0
- package/src/generator/context/domain-coverage.js +94 -0
- package/src/generator/context/domain-page.js +137 -0
- package/src/generator/context/index.js +42 -0
- package/src/generator/context/report.js +121 -0
- package/src/generator/context/shared.js +1397 -0
- package/src/generator/context/slice.js +703 -0
- package/src/generator/context/task-mode.js +466 -0
- package/src/generator/docs.js +327 -0
- package/src/generator/index.js +161 -0
- package/src/generator/native/parity-bundle.js +311 -0
- package/src/generator/output.js +300 -0
- package/src/generator/registry.js +482 -0
- package/src/generator/runtime/app-bundle.js +456 -0
- package/src/generator/runtime/bundle-shared.js +166 -0
- package/src/generator/runtime/compile-check.js +163 -0
- package/src/generator/runtime/deployment.js +287 -0
- package/src/generator/runtime/environment.js +635 -0
- package/src/generator/runtime/index.js +32 -0
- package/src/generator/runtime/runtime-check.js +554 -0
- package/src/generator/runtime/shared.js +515 -0
- package/src/generator/runtime/smoke.js +219 -0
- package/src/generator/schema.js +204 -0
- package/src/generator/sdlc/board.js +66 -0
- package/src/generator/sdlc/doc-page.js +53 -0
- package/src/generator/sdlc/index.js +23 -0
- package/src/generator/sdlc/release-notes.js +62 -0
- package/src/generator/sdlc/traceability-matrix.js +65 -0
- package/src/generator/shared.js +29 -0
- package/src/generator/surfaces/contracts.js +146 -0
- package/src/generator/surfaces/databases/contract.js +40 -0
- package/src/generator/surfaces/databases/index.js +84 -0
- package/src/generator/surfaces/databases/lifecycle-shared.d.ts +1 -0
- package/src/generator/surfaces/databases/lifecycle-shared.js +612 -0
- package/src/generator/surfaces/databases/migration-plan.js +281 -0
- package/src/generator/surfaces/databases/postgres/capabilities.js +14 -0
- package/src/generator/surfaces/databases/postgres/drizzle.js +99 -0
- package/src/generator/surfaces/databases/postgres/index.js +9 -0
- package/src/generator/surfaces/databases/postgres/lifecycle.js +16 -0
- package/src/generator/surfaces/databases/postgres/prisma.js +159 -0
- package/src/generator/surfaces/databases/postgres/sql-migration.js +102 -0
- package/src/generator/surfaces/databases/postgres/sql-schema.js +34 -0
- package/src/generator/surfaces/databases/shared.d.ts +1 -0
- package/src/generator/surfaces/databases/shared.js +350 -0
- package/src/generator/surfaces/databases/snapshot.js +96 -0
- package/src/generator/surfaces/databases/sqlite/capabilities.js +14 -0
- package/src/generator/surfaces/databases/sqlite/index.js +8 -0
- package/src/generator/surfaces/databases/sqlite/lifecycle.js +16 -0
- package/src/generator/surfaces/databases/sqlite/prisma.js +143 -0
- package/src/generator/surfaces/databases/sqlite/sql-migration.js +65 -0
- package/src/generator/surfaces/databases/sqlite/sql-schema.js +27 -0
- package/src/generator/surfaces/index.js +25 -0
- package/src/generator/surfaces/native/swiftui-app.js +38 -0
- package/src/generator/surfaces/native/swiftui-templates/Package.swift.txt +20 -0
- package/src/generator/surfaces/native/swiftui-templates/README.generated.md +26 -0
- package/src/generator/surfaces/native/swiftui-templates/runtime/DynamicScreens.swift +682 -0
- package/src/generator/surfaces/native/swiftui-templates/runtime/TodoAPIClient.swift +156 -0
- package/src/generator/surfaces/native/swiftui-templates/runtime/TodoSwiftUIApp.swift +44 -0
- package/src/generator/surfaces/native/swiftui-templates/runtime/Visibility.swift +183 -0
- package/src/generator/surfaces/services/express.d.ts +1 -0
- package/src/generator/surfaces/services/express.js +766 -0
- package/src/generator/surfaces/services/hono.d.ts +1 -0
- package/src/generator/surfaces/services/hono.js +204 -0
- package/src/generator/surfaces/services/index.js +42 -0
- package/src/generator/surfaces/services/persistence-wiring.js +240 -0
- package/src/generator/surfaces/services/runtime-helpers.js +631 -0
- package/src/generator/surfaces/services/server-contract.js +80 -0
- package/src/generator/surfaces/services/stateless.d.ts +1 -0
- package/src/generator/surfaces/services/stateless.js +97 -0
- package/src/generator/surfaces/shared.js +64 -0
- package/src/generator/surfaces/web/api-client.js +1 -0
- package/src/generator/surfaces/web/forms.js +1 -0
- package/src/generator/surfaces/web/index.d.ts +2 -0
- package/src/generator/surfaces/web/index.js +53 -0
- package/src/generator/surfaces/web/react-components.js +248 -0
- package/src/generator/surfaces/web/react.js +538 -0
- package/src/generator/surfaces/web/routes.js +1 -0
- package/src/generator/surfaces/web/screens.js +1 -0
- package/src/generator/surfaces/web/shared.js +369 -0
- package/src/generator/surfaces/web/sveltekit-actions.js +28 -0
- package/src/generator/surfaces/web/sveltekit-components.js +234 -0
- package/src/generator/surfaces/web/sveltekit.js +426 -0
- package/src/generator/surfaces/web/ui-web-contract.js +65 -0
- package/src/generator/surfaces/web/vanilla.js +239 -0
- package/src/generator/verification.js +84 -0
- package/src/generator.js +1 -0
- package/src/import/core/context.js +52 -0
- package/src/import/core/contracts.js +23 -0
- package/src/import/core/registry.js +81 -0
- package/src/import/core/runner.js +646 -0
- package/src/import/core/shared.js +910 -0
- package/src/import/enrichers/auth-session.js +18 -0
- package/src/import/enrichers/django-rest.js +226 -0
- package/src/import/enrichers/doc-linking.js +20 -0
- package/src/import/enrichers/rails-controllers.js +246 -0
- package/src/import/enrichers/rails-models.js +130 -0
- package/src/import/enrichers/workflow-target-state.js +10 -0
- package/src/import/extractors/api/aspnet-core.js +304 -0
- package/src/import/extractors/api/django-routes.js +318 -0
- package/src/import/extractors/api/express.js +154 -0
- package/src/import/extractors/api/fastify.js +371 -0
- package/src/import/extractors/api/flutter-dio.js +135 -0
- package/src/import/extractors/api/generic-route-fallback.js +90 -0
- package/src/import/extractors/api/graphql-code-first.js +565 -0
- package/src/import/extractors/api/graphql-sdl.js +309 -0
- package/src/import/extractors/api/jaxrs.js +303 -0
- package/src/import/extractors/api/micronaut.js +213 -0
- package/src/import/extractors/api/next-route.js +50 -0
- package/src/import/extractors/api/next-server-action.js +51 -0
- package/src/import/extractors/api/nextauth.js +52 -0
- package/src/import/extractors/api/openapi-code.js +242 -0
- package/src/import/extractors/api/openapi.js +232 -0
- package/src/import/extractors/api/rails-routes.js +230 -0
- package/src/import/extractors/api/react-native-repository.js +128 -0
- package/src/import/extractors/api/retrofit.js +103 -0
- package/src/import/extractors/api/spring-web.js +372 -0
- package/src/import/extractors/api/swift-webapi.js +116 -0
- package/src/import/extractors/api/trpc.js +212 -0
- package/src/import/extractors/db/django-models.js +232 -0
- package/src/import/extractors/db/dotnet-models.js +93 -0
- package/src/import/extractors/db/drizzle.js +242 -0
- package/src/import/extractors/db/ef-core.js +221 -0
- package/src/import/extractors/db/flutter-entities.js +120 -0
- package/src/import/extractors/db/jpa.js +120 -0
- package/src/import/extractors/db/liquibase.js +180 -0
- package/src/import/extractors/db/mybatis-xml.js +145 -0
- package/src/import/extractors/db/prisma.js +185 -0
- package/src/import/extractors/db/rails-schema.js +175 -0
- package/src/import/extractors/db/react-native-entities.js +95 -0
- package/src/import/extractors/db/room.js +193 -0
- package/src/import/extractors/db/snapshot.js +112 -0
- package/src/import/extractors/db/sql.js +180 -0
- package/src/import/extractors/db/swiftdata.js +137 -0
- package/src/import/extractors/ui/android-compose.js +230 -0
- package/src/import/extractors/ui/backend-only.js +70 -0
- package/src/import/extractors/ui/blazor.js +227 -0
- package/src/import/extractors/ui/flutter-screens.js +152 -0
- package/src/import/extractors/ui/maui-xaml.js +135 -0
- package/src/import/extractors/ui/next-app-router.js +83 -0
- package/src/import/extractors/ui/next-pages-router.js +141 -0
- package/src/import/extractors/ui/razor-pages.js +181 -0
- package/src/import/extractors/ui/react-native-screens.js +166 -0
- package/src/import/extractors/ui/react-router.js +139 -0
- package/src/import/extractors/ui/sveltekit.js +123 -0
- package/src/import/extractors/ui/swiftui.js +193 -0
- package/src/import/extractors/ui/uikit.js +175 -0
- package/src/import/extractors/verification/generic.js +290 -0
- package/src/import/extractors/workflows/generic.js +137 -0
- package/src/import/index.js +7 -0
- package/src/import/provenance.js +158 -0
- package/src/new-project.js +2107 -0
- package/src/parser.js +439 -0
- package/src/policy/review-boundaries.js +165 -0
- package/src/project-config.js +535 -0
- package/src/proofs/backend-parity.js +19 -0
- package/src/proofs/contract-audit.js +220 -0
- package/src/proofs/ios-parity.js +7 -0
- package/src/proofs/issues-parity.js +10 -0
- package/src/proofs/web-parity.js +50 -0
- package/src/realization/api/build-api-realization.js +5 -0
- package/src/realization/api/index.js +1 -0
- package/src/realization/backend/build-backend-runtime-realization.js +82 -0
- package/src/realization/backend/index.d.ts +1 -0
- package/src/realization/backend/index.js +4 -0
- package/src/realization/db/build-db-realization.js +17 -0
- package/src/realization/db/index.js +3 -0
- package/src/realization/db/migration-plan.js +5 -0
- package/src/realization/db/snapshot.js +5 -0
- package/src/realization/ui/build-ui-shared-realization.js +305 -0
- package/src/realization/ui/build-web-realization.js +189 -0
- package/src/realization/ui/index.js +2 -0
- package/src/reconcile/docs.js +280 -0
- package/src/reconcile/index.js +3 -0
- package/src/reconcile/journeys.js +441 -0
- package/src/resolver/docs.js +1 -0
- package/src/resolver/enrich/acceptance-criterion.js +14 -0
- package/src/resolver/enrich/bug.js +12 -0
- package/src/resolver/enrich/component.js +2 -0
- package/src/resolver/enrich/index.js +1 -0
- package/src/resolver/enrich/pitch.js +18 -0
- package/src/resolver/enrich/requirement.js +20 -0
- package/src/resolver/enrich/task.js +16 -0
- package/src/resolver/expressions.js +1 -0
- package/src/resolver/index.js +2422 -0
- package/src/resolver/normalize.js +1 -0
- package/src/resolver.js +1 -0
- package/src/sdlc/adopt.js +65 -0
- package/src/sdlc/check.js +86 -0
- package/src/sdlc/dod/acceptance-criterion.js +22 -0
- package/src/sdlc/dod/bug.js +26 -0
- package/src/sdlc/dod/document.js +23 -0
- package/src/sdlc/dod/index.js +25 -0
- package/src/sdlc/dod/pitch.js +23 -0
- package/src/sdlc/dod/requirement.js +34 -0
- package/src/sdlc/dod/task.js +39 -0
- package/src/sdlc/explain.js +116 -0
- package/src/sdlc/history.js +80 -0
- package/src/sdlc/paths.js +11 -0
- package/src/sdlc/release.js +106 -0
- package/src/sdlc/scaffold.js +89 -0
- package/src/sdlc/status-filter.js +54 -0
- package/src/sdlc/transition.js +112 -0
- package/src/sdlc/transitions/acceptance-criterion.js +28 -0
- package/src/sdlc/transitions/bug.js +31 -0
- package/src/sdlc/transitions/document.js +29 -0
- package/src/sdlc/transitions/index.js +56 -0
- package/src/sdlc/transitions/pitch.js +34 -0
- package/src/sdlc/transitions/requirement.js +31 -0
- package/src/sdlc/transitions/task.js +34 -0
- package/src/template-trust.js +597 -0
- package/src/validator/expressions.js +1 -0
- package/src/validator/index.js +3424 -0
- package/src/validator/kinds.js +346 -0
- package/src/validator/per-kind/acceptance-criterion.js +91 -0
- package/src/validator/per-kind/bug.js +77 -0
- package/src/validator/per-kind/component.js +274 -0
- package/src/validator/per-kind/domain.js +205 -0
- package/src/validator/per-kind/pitch.js +101 -0
- package/src/validator/per-kind/requirement.js +75 -0
- package/src/validator/per-kind/task.js +96 -0
- package/src/validator/registry.js +1 -0
- package/src/validator/utils.js +12 -0
- package/src/validator.js +1 -0
- package/src/workflows.js +7597 -0
- package/src/workspace-docs.js +265 -0
- package/template-helpers/react.js +5 -0
- package/template-helpers/sveltekit.js +5 -0
package/src/parser.js
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parseDocsPath } from "./workspace-docs.js";
|
|
4
|
+
|
|
5
|
+
function createPos(line, column, offset) {
|
|
6
|
+
return { line, column, offset };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function createToken(type, value, start, end) {
|
|
10
|
+
return { type, value, start, end };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function createError(filePath, message, token) {
|
|
14
|
+
const line = token?.start?.line ?? 1;
|
|
15
|
+
const column = token?.start?.column ?? 1;
|
|
16
|
+
return new Error(`${filePath}:${line}:${column} ${message}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function lex(source, filePath = "<memory>") {
|
|
20
|
+
const tokens = [];
|
|
21
|
+
let i = 0;
|
|
22
|
+
let line = 1;
|
|
23
|
+
let column = 1;
|
|
24
|
+
|
|
25
|
+
function pos() {
|
|
26
|
+
return createPos(line, column, i);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function advanceChar(char) {
|
|
30
|
+
i += 1;
|
|
31
|
+
if (char === "\n") {
|
|
32
|
+
line += 1;
|
|
33
|
+
column = 1;
|
|
34
|
+
} else {
|
|
35
|
+
column += 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function push(type, value, start, end) {
|
|
40
|
+
tokens.push(createToken(type, value, start, end));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
while (i < source.length) {
|
|
44
|
+
const char = source[i];
|
|
45
|
+
|
|
46
|
+
if (char === " " || char === "\t" || char === "\r") {
|
|
47
|
+
advanceChar(char);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (char === "\n") {
|
|
52
|
+
const start = pos();
|
|
53
|
+
advanceChar(char);
|
|
54
|
+
push("newline", "\n", start, pos());
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (char === "#") {
|
|
59
|
+
while (i < source.length && source[i] !== "\n") {
|
|
60
|
+
advanceChar(source[i]);
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (char === "{") {
|
|
66
|
+
const start = pos();
|
|
67
|
+
advanceChar(char);
|
|
68
|
+
push("lbrace", "{", start, pos());
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (char === "}") {
|
|
73
|
+
const start = pos();
|
|
74
|
+
advanceChar(char);
|
|
75
|
+
push("rbrace", "}", start, pos());
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (char === "[") {
|
|
80
|
+
const start = pos();
|
|
81
|
+
advanceChar(char);
|
|
82
|
+
push("lbracket", "[", start, pos());
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (char === "]") {
|
|
87
|
+
const start = pos();
|
|
88
|
+
advanceChar(char);
|
|
89
|
+
push("rbracket", "]", start, pos());
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (char === ",") {
|
|
94
|
+
const start = pos();
|
|
95
|
+
advanceChar(char);
|
|
96
|
+
push("comma", ",", start, pos());
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (char === "\"") {
|
|
101
|
+
const start = pos();
|
|
102
|
+
advanceChar(char);
|
|
103
|
+
|
|
104
|
+
let value = "";
|
|
105
|
+
let closed = false;
|
|
106
|
+
while (i < source.length) {
|
|
107
|
+
const current = source[i];
|
|
108
|
+
if (current === "\"") {
|
|
109
|
+
advanceChar(current);
|
|
110
|
+
push("string", value, start, pos());
|
|
111
|
+
closed = true;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (current === "\\") {
|
|
116
|
+
const next = source[i + 1];
|
|
117
|
+
if (next === undefined) {
|
|
118
|
+
throw createError(filePath, "Unterminated string literal", createToken("string", value, start, pos()));
|
|
119
|
+
}
|
|
120
|
+
const escaped = next === "n" ? "\n" : next === "t" ? "\t" : next;
|
|
121
|
+
value += escaped;
|
|
122
|
+
advanceChar(current);
|
|
123
|
+
advanceChar(next);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
value += current;
|
|
128
|
+
advanceChar(current);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!closed) {
|
|
132
|
+
throw createError(filePath, "Unterminated string literal", createToken("string", value, start, pos()));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const start = pos();
|
|
139
|
+
let value = "";
|
|
140
|
+
while (i < source.length) {
|
|
141
|
+
const current = source[i];
|
|
142
|
+
if (
|
|
143
|
+
current === " " ||
|
|
144
|
+
current === "\t" ||
|
|
145
|
+
current === "\r" ||
|
|
146
|
+
current === "\n" ||
|
|
147
|
+
current === "#" ||
|
|
148
|
+
current === "{" ||
|
|
149
|
+
current === "}" ||
|
|
150
|
+
current === "[" ||
|
|
151
|
+
current === "]" ||
|
|
152
|
+
current === "," ||
|
|
153
|
+
current === "\""
|
|
154
|
+
) {
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
value += current;
|
|
158
|
+
advanceChar(current);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
push("word", value, start, pos());
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
tokens.push(createToken("eof", "", pos(), pos()));
|
|
165
|
+
return tokens;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
class Parser {
|
|
169
|
+
constructor(tokens, filePath) {
|
|
170
|
+
this.tokens = tokens;
|
|
171
|
+
this.filePath = filePath;
|
|
172
|
+
this.index = 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
current() {
|
|
176
|
+
return this.tokens[this.index];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
previous() {
|
|
180
|
+
return this.tokens[this.index - 1];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
advance() {
|
|
184
|
+
const token = this.current();
|
|
185
|
+
this.index += 1;
|
|
186
|
+
return token;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
check(type, value) {
|
|
190
|
+
const token = this.current();
|
|
191
|
+
return token.type === type && (value === undefined || token.value === value);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
match(type, value) {
|
|
195
|
+
if (!this.check(type, value)) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
return this.advance();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
expect(type, value, message) {
|
|
202
|
+
const token = this.current();
|
|
203
|
+
if (!this.check(type, value)) {
|
|
204
|
+
throw createError(this.filePath, message, token);
|
|
205
|
+
}
|
|
206
|
+
return this.advance();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
skipNewlines() {
|
|
210
|
+
while (this.match("newline")) {
|
|
211
|
+
// Skip layout-only line breaks between statements and fields.
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
parseDocument() {
|
|
216
|
+
const statements = [];
|
|
217
|
+
this.skipNewlines();
|
|
218
|
+
|
|
219
|
+
while (!this.check("eof")) {
|
|
220
|
+
statements.push(this.parseStatement());
|
|
221
|
+
this.skipNewlines();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
type: "document",
|
|
226
|
+
file: this.filePath,
|
|
227
|
+
statements
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
parseStatement() {
|
|
232
|
+
const kind = this.expect("word", undefined, "Expected statement kind");
|
|
233
|
+
const id = this.expect("word", undefined, "Expected statement identifier");
|
|
234
|
+
const start = kind.start;
|
|
235
|
+
|
|
236
|
+
let from = null;
|
|
237
|
+
if (this.match("word", "from")) {
|
|
238
|
+
from = this.expect("word", undefined, "Expected identifier after 'from'");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.skipNewlines();
|
|
242
|
+
this.expect("lbrace", undefined, "Expected '{' to start statement body");
|
|
243
|
+
this.skipNewlines();
|
|
244
|
+
|
|
245
|
+
const fields = [];
|
|
246
|
+
while (!this.check("rbrace")) {
|
|
247
|
+
fields.push(this.parseField());
|
|
248
|
+
this.skipNewlines();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const end = this.expect("rbrace", undefined, "Expected '}' to close statement body").end;
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
type: "statement",
|
|
255
|
+
kind: kind.value,
|
|
256
|
+
id: id.value,
|
|
257
|
+
from: from ? this.nodeFromToken(from) : null,
|
|
258
|
+
fields,
|
|
259
|
+
loc: this.loc(start, end)
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
parseField() {
|
|
264
|
+
const key = this.expect("word", undefined, "Expected field name");
|
|
265
|
+
const start = key.start;
|
|
266
|
+
|
|
267
|
+
if (this.check("lbrace")) {
|
|
268
|
+
const value = this.parseBlock();
|
|
269
|
+
return {
|
|
270
|
+
type: "field",
|
|
271
|
+
key: key.value,
|
|
272
|
+
value,
|
|
273
|
+
loc: this.loc(start, value.loc.end)
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const values = this.parseSequenceUntil(["newline", "rbrace"]);
|
|
278
|
+
if (values.length === 0) {
|
|
279
|
+
throw createError(this.filePath, `Expected value for field '${key.value}'`, this.current());
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
type: "field",
|
|
284
|
+
key: key.value,
|
|
285
|
+
value: values.length === 1 ? values[0] : { type: "sequence", items: values, loc: this.loc(values[0].loc.start, values[values.length - 1].loc.end) },
|
|
286
|
+
loc: this.loc(start, values[values.length - 1].loc.end)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
parseBlock() {
|
|
291
|
+
const open = this.expect("lbrace", undefined, "Expected '{' to start block");
|
|
292
|
+
this.skipNewlines();
|
|
293
|
+
const entries = [];
|
|
294
|
+
|
|
295
|
+
while (!this.check("rbrace")) {
|
|
296
|
+
const entry = this.parseSequenceUntil(["newline", "rbrace"]);
|
|
297
|
+
if (entry.length === 0) {
|
|
298
|
+
throw createError(this.filePath, "Expected block entry", this.current());
|
|
299
|
+
}
|
|
300
|
+
entries.push({
|
|
301
|
+
type: "block_entry",
|
|
302
|
+
items: entry,
|
|
303
|
+
loc: this.loc(entry[0].loc.start, entry[entry.length - 1].loc.end)
|
|
304
|
+
});
|
|
305
|
+
this.skipNewlines();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const close = this.expect("rbrace", undefined, "Expected '}' to close block");
|
|
309
|
+
return {
|
|
310
|
+
type: "block",
|
|
311
|
+
entries,
|
|
312
|
+
loc: this.loc(open.start, close.end)
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
parseSequenceUntil(stopTypes) {
|
|
317
|
+
const items = [];
|
|
318
|
+
|
|
319
|
+
while (!stopTypes.includes(this.current().type) && !this.check("eof")) {
|
|
320
|
+
if (this.match("comma")) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
items.push(this.parseAtom());
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return items;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
parseAtom() {
|
|
330
|
+
const token = this.current();
|
|
331
|
+
|
|
332
|
+
if (token.type === "string") {
|
|
333
|
+
this.advance();
|
|
334
|
+
return {
|
|
335
|
+
type: "string",
|
|
336
|
+
value: token.value,
|
|
337
|
+
loc: this.loc(token.start, token.end)
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (token.type === "word") {
|
|
342
|
+
this.advance();
|
|
343
|
+
return {
|
|
344
|
+
type: "symbol",
|
|
345
|
+
value: token.value,
|
|
346
|
+
loc: this.loc(token.start, token.end)
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (token.type === "lbracket") {
|
|
351
|
+
return this.parseList();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
throw createError(this.filePath, `Unexpected token '${token.value || token.type}'`, token);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
parseList() {
|
|
358
|
+
const open = this.expect("lbracket", undefined, "Expected '[' to start list");
|
|
359
|
+
const items = [];
|
|
360
|
+
|
|
361
|
+
while (!this.check("rbracket")) {
|
|
362
|
+
if (this.match("newline") || this.match("comma")) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
items.push(this.parseAtom());
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const close = this.expect("rbracket", undefined, "Expected ']' to close list");
|
|
369
|
+
return {
|
|
370
|
+
type: "list",
|
|
371
|
+
items,
|
|
372
|
+
loc: this.loc(open.start, close.end)
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
nodeFromToken(token) {
|
|
377
|
+
return {
|
|
378
|
+
type: "symbol",
|
|
379
|
+
value: token.value,
|
|
380
|
+
loc: this.loc(token.start, token.end)
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
loc(start, end) {
|
|
385
|
+
return {
|
|
386
|
+
file: this.filePath,
|
|
387
|
+
start,
|
|
388
|
+
end
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function parseSource(source, filePath = "<memory>") {
|
|
394
|
+
const tokens = lex(source, filePath);
|
|
395
|
+
const parser = new Parser(tokens, filePath);
|
|
396
|
+
return parser.parseDocument();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function parseFile(filePath) {
|
|
400
|
+
const source = fs.readFileSync(filePath, "utf8");
|
|
401
|
+
return parseSource(source, filePath);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export function collectTopogramFiles(inputPath) {
|
|
405
|
+
const absolutePath = path.resolve(inputPath);
|
|
406
|
+
const stat = fs.statSync(absolutePath);
|
|
407
|
+
|
|
408
|
+
if (stat.isFile()) {
|
|
409
|
+
return absolutePath.endsWith(".tg") ? [absolutePath] : [];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const files = [];
|
|
413
|
+
for (const entry of fs.readdirSync(absolutePath, { withFileTypes: true })) {
|
|
414
|
+
const childPath = path.join(absolutePath, entry.name);
|
|
415
|
+
if (entry.isDirectory()) {
|
|
416
|
+
if (entry.name === "candidates" || entry.name === "docs-generated") {
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
files.push(...collectTopogramFiles(childPath));
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (entry.isFile() && entry.name.endsWith(".tg")) {
|
|
423
|
+
files.push(childPath);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return files.sort();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export function parsePath(inputPath) {
|
|
431
|
+
const files = collectTopogramFiles(inputPath);
|
|
432
|
+
const docs = parseDocsPath(inputPath);
|
|
433
|
+
return {
|
|
434
|
+
type: "workspace",
|
|
435
|
+
root: path.resolve(inputPath),
|
|
436
|
+
files: files.map((filePath) => parseFile(filePath)),
|
|
437
|
+
docs
|
|
438
|
+
};
|
|
439
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
export const REVIEW_AUTOMATION_CLASSES = Object.freeze([
|
|
2
|
+
"safe",
|
|
3
|
+
"review_required",
|
|
4
|
+
"manual_decision",
|
|
5
|
+
"no_go"
|
|
6
|
+
]);
|
|
7
|
+
|
|
8
|
+
export const ADOPTION_STATE_VOCABULARY = Object.freeze([
|
|
9
|
+
"accept",
|
|
10
|
+
"map",
|
|
11
|
+
"customize",
|
|
12
|
+
"stage",
|
|
13
|
+
"reject"
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
function buildReviewBoundary(automationClass, reasons = [], extras = {}) {
|
|
17
|
+
return {
|
|
18
|
+
automation_class: automationClass,
|
|
19
|
+
reasons: [...new Set((reasons || []).filter(Boolean))].sort(),
|
|
20
|
+
...extras
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function defaultOwnershipBoundary() {
|
|
25
|
+
return {
|
|
26
|
+
canonical_topogram: {
|
|
27
|
+
owner: "human",
|
|
28
|
+
agent_mode: "propose_review_adopt"
|
|
29
|
+
},
|
|
30
|
+
generated_artifacts: {
|
|
31
|
+
owner: "engine",
|
|
32
|
+
agent_mode: "regenerate_or_compare"
|
|
33
|
+
},
|
|
34
|
+
maintained_code: {
|
|
35
|
+
owner: "human",
|
|
36
|
+
agent_mode: "bounded_edit_only"
|
|
37
|
+
},
|
|
38
|
+
proposal_surfaces: {
|
|
39
|
+
owner: "human",
|
|
40
|
+
agent_mode: "stage_map_customize_reject"
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function reviewBoundaryForCapability(capability) {
|
|
46
|
+
if ((capability.creates || []).length > 0 || (capability.updates || []).length > 0 || (capability.deletes || []).length > 0) {
|
|
47
|
+
return buildReviewBoundary("review_required", ["writes_modeled_state"]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return buildReviewBoundary("safe", ["read_only_surface"]);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function reviewBoundaryForProjection(projection) {
|
|
54
|
+
if ((projection.uiScreens || []).length > 0 || (projection.uiActions || []).length > 0) {
|
|
55
|
+
return buildReviewBoundary("manual_decision", ["ui_surface"]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if ((projection.dbTables || []).length > 0 || (projection.dbColumns || []).length > 0 || (projection.dbRelations || []).length > 0) {
|
|
59
|
+
return buildReviewBoundary("review_required", ["db_surface"]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if ((projection.http || []).length > 0) {
|
|
63
|
+
return buildReviewBoundary("review_required", ["api_surface"]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return buildReviewBoundary("safe", ["metadata_only"]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function reviewBoundaryForEntity(entity) {
|
|
70
|
+
if ((entity.relations || []).length > 0) {
|
|
71
|
+
return buildReviewBoundary("manual_decision", ["relation_surface"]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return buildReviewBoundary("review_required", ["schema_surface"]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function reviewBoundaryForWorkflowDoc(workflow) {
|
|
78
|
+
return workflow.reviewRequired
|
|
79
|
+
? buildReviewBoundary("manual_decision", ["doc_review_required"])
|
|
80
|
+
: buildReviewBoundary("review_required", ["workflow_semantics"]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function reviewBoundaryForJourneyDoc(journey) {
|
|
84
|
+
return journey.reviewRequired
|
|
85
|
+
? buildReviewBoundary("manual_decision", ["journey_review_required"])
|
|
86
|
+
: buildReviewBoundary("review_required", ["journey_surface"]);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function reviewBoundaryForDomain() {
|
|
90
|
+
return buildReviewBoundary("review_required", ["domain_surface"]);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Phase 2 — SDLC slice boundaries.
|
|
94
|
+
// pitch / requirement / task / bug / document each define a unit of agent
|
|
95
|
+
// or human work and surface natural review gates.
|
|
96
|
+
|
|
97
|
+
export function reviewBoundaryForPitch() {
|
|
98
|
+
return buildReviewBoundary("manual_decision", ["pitch_scope"]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function reviewBoundaryForRequirement() {
|
|
102
|
+
return buildReviewBoundary("manual_decision", ["requirement_scope"]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function reviewBoundaryForAcceptanceCriterion() {
|
|
106
|
+
return buildReviewBoundary("review_required", ["acceptance_scope"]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function reviewBoundaryForTask() {
|
|
110
|
+
return buildReviewBoundary("review_required", ["task_scope"]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function reviewBoundaryForBug() {
|
|
114
|
+
return buildReviewBoundary("review_required", ["bug_scope"]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function reviewBoundaryForDocument(doc) {
|
|
118
|
+
if (doc?.reviewRequired) {
|
|
119
|
+
return buildReviewBoundary("manual_decision", ["document_scope", "doc_review_required"]);
|
|
120
|
+
}
|
|
121
|
+
return buildReviewBoundary("review_required", ["document_scope"]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function reviewBoundaryForMaintainedClassification(classification) {
|
|
125
|
+
if (classification === "accepted_change") {
|
|
126
|
+
return buildReviewBoundary("review_required", ["maintained_surface", "accepted_change"]);
|
|
127
|
+
}
|
|
128
|
+
if (classification === "guarded_manual_decision") {
|
|
129
|
+
return buildReviewBoundary("manual_decision", ["maintained_surface", "product_judgment_required"]);
|
|
130
|
+
}
|
|
131
|
+
if (classification === "no_go") {
|
|
132
|
+
return buildReviewBoundary("no_go", ["maintained_surface", "unsafe_semantic_drift"]);
|
|
133
|
+
}
|
|
134
|
+
if (classification === "independent_review") {
|
|
135
|
+
return buildReviewBoundary("review_required", ["independent_review_artifact"]);
|
|
136
|
+
}
|
|
137
|
+
return buildReviewBoundary("review_required", ["maintained_surface"]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function reviewBoundaryForImportProposal(item) {
|
|
141
|
+
if ((item.blocking_dependencies || []).length > 0) {
|
|
142
|
+
return buildReviewBoundary("manual_decision", ["proposal_has_blocking_dependencies"]);
|
|
143
|
+
}
|
|
144
|
+
if ((item.projection_impacts || []).length > 0 || (item.ui_impacts || []).length > 0 || (item.workflow_impacts || []).length > 0) {
|
|
145
|
+
return buildReviewBoundary("manual_decision", ["proposal_impacts_existing_surface"]);
|
|
146
|
+
}
|
|
147
|
+
if (item.track === "docs") {
|
|
148
|
+
return buildReviewBoundary("review_required", ["proposal_doc_surface"]);
|
|
149
|
+
}
|
|
150
|
+
if (item.kind === "entity" || item.kind === "shape" || item.kind === "capability") {
|
|
151
|
+
return buildReviewBoundary("review_required", ["proposal_modeled_surface"]);
|
|
152
|
+
}
|
|
153
|
+
return buildReviewBoundary("safe", ["proposal_metadata_only"]);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function ownershipBoundaryForMaintainedSurface() {
|
|
157
|
+
const ownership = defaultOwnershipBoundary();
|
|
158
|
+
return {
|
|
159
|
+
...ownership,
|
|
160
|
+
maintained_code: {
|
|
161
|
+
owner: "human",
|
|
162
|
+
agent_mode: "explicit_boundary_required"
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|