genesis-compiler 1.7.7 → 1.8.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.
- package/README.md +56 -9
- package/docs/assurance-model.md +4 -2
- package/docs/prompt-integration.md +21 -3
- package/docs/templates.md +11 -0
- package/package.json +5 -3
- package/plugins/genesis/.codex-plugin/plugin.json +1 -1
- package/prompts/describe.txt +5 -0
- package/prompts/program.txt +9 -1
- package/skills/genesis-program/SKILL.md +41 -4
- package/skills/genesis-project/SKILL.md +2 -2
- package/src/cli.js +21 -8
- package/src/index/architecture-observations.js +167 -0
- package/src/index/architecture.js +227 -0
- package/src/index/code-index.js +66 -6
- package/src/index/code-indexers/ast-grep.js +223 -19
- package/src/index/code-indexers/npm-packages.js +200 -0
- package/src/index/context.js +1 -1
- package/src/index/host-context-resolver.js +14 -1
- package/src/index/program.js +28 -3
- package/src/index/project-inspection.js +26 -1
- package/src/index/session-context.js +1 -1
- package/src/index/source-explanation.js +31 -0
- package/src/index/stack-package-discovery.js +25 -0
- package/src/index/stack-ui-discovery.js +38 -0
- package/src/index/stack.js +6 -0
- package/src/index/subsystems.js +16 -3
- package/src/index/utils.js +2 -2
- package/src/index.js +8 -1
package/README.md
CHANGED
|
@@ -49,10 +49,16 @@ genesis/program/notes/list-notes.md
|
|
|
49
49
|
genesis/program/notes/create-note.md
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
The directory
|
|
52
|
+
The directory organizes explanations; `genesis/subsystems.md` assigns each
|
|
53
|
+
module to one directory-bound subsystem. Each file describes one public
|
|
53
54
|
operation provided by that subsystem: an exported method, API action, route,
|
|
54
55
|
command, UI operation, or equivalent entry point. Each module cites one or more
|
|
55
|
-
real files under `## Sources`.
|
|
56
|
+
real files under `## Sources`. Every module requires exactly one
|
|
57
|
+
`- Main: \`path/to/source\`` bullet naming its implementation's reading anchor.
|
|
58
|
+
The main source belongs to the module's owning subsystem; other source bullets
|
|
59
|
+
may cite shared libraries. It need not be an executable entry point. Existing
|
|
60
|
+
modules need an explicitly chosen anchor before their Program can validate;
|
|
61
|
+
indexing never guesses from source-list order. There is no 1:1 source mapping: one operation may
|
|
56
62
|
span several files, helper files may be omitted, and one source may support
|
|
57
63
|
multiple operations.
|
|
58
64
|
|
|
@@ -78,7 +84,7 @@ catalog as separate packages:
|
|
|
78
84
|
npm install --global genesis-compiler genesis-stack
|
|
79
85
|
```
|
|
80
86
|
|
|
81
|
-
`genesis-compiler` has no dependency on `genesis-stack` or on any framework.
|
|
87
|
+
`genesis-compiler` has no runtime dependency on `genesis-stack` or on any framework.
|
|
82
88
|
The catalog is static Markdown and contains the complete curated set of
|
|
83
89
|
language, database, UI, and framework pieces. A host may supply another catalog
|
|
84
90
|
through the same contract.
|
|
@@ -880,13 +886,23 @@ result. A newer project is never downgraded. Every project-scoped CLI
|
|
|
880
886
|
invocation warns when the recorded format does not match the running CLI, and
|
|
881
887
|
ordinary project operations stop until the migration is resolved.
|
|
882
888
|
|
|
889
|
+
## Development
|
|
890
|
+
|
|
891
|
+
Run `npm ci` in this source checkout, then `npm test`. The test command runs the
|
|
892
|
+
original test files with file concurrency fixed at one. The local first-party
|
|
893
|
+
catalogue is a development-only dependency so catalogue and starter tests work
|
|
894
|
+
after a clean install; it is not required by the published compiler runtime.
|
|
895
|
+
|
|
883
896
|
## Public API
|
|
884
897
|
|
|
885
898
|
```js
|
|
886
899
|
import {
|
|
900
|
+
ARCHITECTURE_OBSERVATIONS_SCHEMA,
|
|
901
|
+
SOURCE_EXPLANATION_PROMPT_VERSION,
|
|
887
902
|
adoptProject,
|
|
888
903
|
addStack,
|
|
889
904
|
check,
|
|
905
|
+
connectArchitectureObservations,
|
|
890
906
|
generatePrompt,
|
|
891
907
|
getContext,
|
|
892
908
|
indexCodebase,
|
|
@@ -904,11 +920,16 @@ import {
|
|
|
904
920
|
verifyParsers,
|
|
905
921
|
parserEnvironment,
|
|
906
922
|
migrate,
|
|
923
|
+
mergeArchitectureObservations,
|
|
907
924
|
projectSessionContext,
|
|
908
925
|
projectTurnContext,
|
|
909
926
|
setCollaboration,
|
|
910
927
|
setEngineeringProfile,
|
|
928
|
+
sourceExplanationGuidance,
|
|
929
|
+
sourceExplanationReviewGuidance,
|
|
911
930
|
syncSkills,
|
|
931
|
+
validateArchitectureFacts,
|
|
932
|
+
validateArchitectureObservations,
|
|
912
933
|
verify,
|
|
913
934
|
withTrustedGitRepository,
|
|
914
935
|
} from 'genesis-compiler';
|
|
@@ -942,6 +963,24 @@ text.
|
|
|
942
963
|
the functions already declared there, selected Stack guidance, available Agent
|
|
943
964
|
Skills, and verification commands. `indexCodebase()` regenerates or returns the
|
|
944
965
|
detailed Machine City and simple Program City projections.
|
|
966
|
+
Its `observations` option accepts either a validated observation document or an
|
|
967
|
+
async callback receiving the fresh architecture facts. A host can run its finite
|
|
968
|
+
inspector inside that callback, avoiding a second source extraction. Genesis
|
|
969
|
+
joins the result and rechecks source, Stack, subsystem and package inputs before
|
|
970
|
+
writing compact derived JSON; failures and cancellation retain the prior index.
|
|
971
|
+
`ARCHITECTURE_OBSERVATIONS_SCHEMA` identifies the neutral inspector document.
|
|
972
|
+
`validateArchitectureObservations()` checks its evidence against supplied files
|
|
973
|
+
and packages; `connectArchitectureObservations()` joins exact owners and
|
|
974
|
+
controller operations. `mergeArchitectureObservations()` combines distinct
|
|
975
|
+
inspectors while rejecting conflicting facts or package snapshots.
|
|
976
|
+
`validateArchitectureFacts()` validates the combined architecture snapshot.
|
|
977
|
+
These operations neither run a framework inspector nor claim complete runtime
|
|
978
|
+
behavior.
|
|
979
|
+
`sourceExplanationGuidance()` and `sourceExplanationReviewGuidance()` supply
|
|
980
|
+
read-only informational interpretation and review prompts. The consumer owns
|
|
981
|
+
bounded source selection, model execution and cache freshness, including
|
|
982
|
+
`SOURCE_EXPLANATION_PROMPT_VERSION`. These explanations do not become Program
|
|
983
|
+
contracts or coding-agent instructions.
|
|
945
984
|
`inspectEnvironment()` returns normalized Stack resource declarations,
|
|
946
985
|
public non-secret defaults, value-free missing-input diagnostics, and dotenv
|
|
947
986
|
projection paths without returning any supplied environment value.
|
|
@@ -1008,6 +1047,9 @@ remain evidence; the map does not define application behavior or database DDL.
|
|
|
1008
1047
|
|
|
1009
1048
|
Owns reusable form authoring, rendering, and answer validation.
|
|
1010
1049
|
|
|
1050
|
+
### Source
|
|
1051
|
+
- Directory `src/forms`
|
|
1052
|
+
|
|
1011
1053
|
### Program
|
|
1012
1054
|
- `genesis/program/forms/render.md`
|
|
1013
1055
|
|
|
@@ -1019,12 +1061,17 @@ Owns reusable form authoring, rendering, and answer validation.
|
|
|
1019
1061
|
```
|
|
1020
1062
|
|
|
1021
1063
|
Each subsystem has a unique lowercase hyphenated id, title, responsibility
|
|
1022
|
-
prose, and
|
|
1064
|
+
prose, and Source, Program, Data owned and Data used sections. Source declares
|
|
1065
|
+
exactly one canonical project-relative Directory (`.` is allowed); roots are
|
|
1066
|
+
unique. Nested owners take the files beneath their more specific root. Each Program module is assigned
|
|
1023
1067
|
exactly once, by its complete project-relative path. Each table has at most one
|
|
1024
1068
|
owner. Data used names a table owned by another declared subsystem. Empty
|
|
1025
1069
|
sections explicitly say `- Nothing.`; an empty initialized map contains
|
|
1026
1070
|
`# Subsystems` followed by `- Nothing.`. Data-only and code-only subsystems are
|
|
1027
|
-
valid.
|
|
1071
|
+
valid. The ready map is the canonical owner list for authored and indexed views. Stack
|
|
1072
|
+
directory suggestions never split it. Every indexed source needs an owner.
|
|
1073
|
+
Program Sources may include other subsystems’ libraries; that does not change
|
|
1074
|
+
the operation’s unique declared owner.
|
|
1028
1075
|
|
|
1029
1076
|
Table references contain three distinct values: declared Stack resource **id**,
|
|
1030
1077
|
schema, and table. `default` selects that resource's default schema; other schema
|
|
@@ -1045,7 +1092,7 @@ changes. Opening context stays concise; detailed associations load on demand.
|
|
|
1045
1092
|
Adoption, Program refresh, work prompts, session guidance and installed workflow
|
|
1046
1093
|
skills maintain the map alongside Program in the same turn. Review checks it
|
|
1047
1094
|
read-only; Blueprint-only work leaves it alone. Ordinary private helper edits
|
|
1048
|
-
need no map change. Existing
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1095
|
+
need no map change. Existing maps must declare their source roots. Program modules require a ready
|
|
1096
|
+
map and one owner even when the old map was missing or empty. If the map is missing, empty or invalid, Program inspection reports the
|
|
1097
|
+
required authoring work instead of assigning operations from folder names.
|
|
1098
|
+
Initialization creates an empty map without replacing one.
|
package/docs/assurance-model.md
CHANGED
|
@@ -5,8 +5,10 @@ Genesis reports only facts it can establish mechanically:
|
|
|
5
5
|
1. Blueprint, Engineering approach, Stack, and Program files satisfy their
|
|
6
6
|
small structural formats, and the selected engineering profile exists in
|
|
7
7
|
the installed versioned catalog.
|
|
8
|
-
2. Program
|
|
9
|
-
source files.
|
|
8
|
+
2. Each Program operation has one declared directory-bound subsystem owner and
|
|
9
|
+
cites real source files. It names exactly one main source in that subsystem;
|
|
10
|
+
supporting citations may cross subsystem boundaries. Optional helper maps
|
|
11
|
+
and the quality of the chosen reading anchor are not mechanically certified.
|
|
10
12
|
3. Stack-declared environment inputs are present and non-placeholder. This does
|
|
11
13
|
not prove that an external service is reachable; a verification command must
|
|
12
14
|
establish that when it matters.
|
|
@@ -82,7 +82,25 @@ JSON on stdin:
|
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
For `genesis hook turn`, the input has the same shape with `scope: "turn"`.
|
|
85
|
-
The executable returns
|
|
85
|
+
The executable returns exactly one JSON result:
|
|
86
|
+
|
|
87
|
+
- `{"kind":"unclaimed"}`: this native conversation is not managed by the host;
|
|
88
|
+
Genesis delivers its standalone guide.
|
|
89
|
+
- `{"kind":"contribution","text":"..."}`: Genesis composes this bounded host
|
|
90
|
+
contribution with its own session guide (or bounded turn contribution).
|
|
91
|
+
- `{"kind":"delivery","text":"..."}`: the host owns the complete session
|
|
92
|
+
delivery; Genesis forwards it without adding its standalone copy. Empty text
|
|
93
|
+
means the host already delivers through a native system field. This result
|
|
94
|
+
is valid only for session scope.
|
|
95
|
+
|
|
96
|
+
Malformed results and configured-resolver failures fail the hook. An unavailable
|
|
97
|
+
claimed owner must fail rather than returning `unclaimed`. Ownership belongs to
|
|
98
|
+
the native conversation: these results do not change project hook files, so
|
|
99
|
+
independent CLI and managed conversations can use the same project. The host
|
|
100
|
+
owns durable bindings, ancestry verification and restoration across reconnection.
|
|
101
|
+
No host framework dependency is added to Genesis.
|
|
102
|
+
|
|
103
|
+
Codex and Claude Code
|
|
86
104
|
hook input and OpenCode's transform callbacks supply the native session id;
|
|
87
105
|
Genesis extracts that id but does not understand the host registry or the
|
|
88
106
|
provider's prompt-placement rules. The Codex and Claude prompt hooks and OpenCode message
|
|
@@ -104,8 +122,8 @@ Codex hook settings. A host can render these fields as an actionable failure
|
|
|
104
122
|
instead of displaying the runtime stack.
|
|
105
123
|
|
|
106
124
|
The OpenCode session adapter also includes verified native `parentSessionIds`
|
|
107
|
-
in `GENESIS_HOST_CONTEXT_INPUT` for host command shims
|
|
108
|
-
receives the actual child's `providerSessionId`; Genesis does not interpret a
|
|
125
|
+
in `GENESIS_HOST_CONTEXT_INPUT` for host command shims and in the resolver
|
|
126
|
+
request when present (at most 32 IDs). The resolver receives the actual child's `providerSessionId`; Genesis does not interpret a
|
|
109
127
|
host's ownership rules.
|
|
110
128
|
|
|
111
129
|
After updating Genesis, hosts can inspect all generated integrations with
|
package/docs/templates.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Project opening and templates
|
|
2
2
|
|
|
3
|
+
The official JSKIT choices are **Basic app**, **Database app**, and **Accounts
|
|
4
|
+
app**. Basic keeps the existing `jskit/public` ID and `public` branch. Database
|
|
5
|
+
uses `jskit/database` and the `database` branch, adding MySQL without login.
|
|
6
|
+
Accounts keeps `jskit/accounts` and the `accounts` branch. These are complete
|
|
7
|
+
starter projects with ready tests; importing source still runs no setup commands.
|
|
8
|
+
|
|
3
9
|
`genesis init` installs the portable Genesis documents, workflow skills, and
|
|
4
10
|
agent integration in an ordinary Git repository. It preserves existing source.
|
|
5
11
|
Initialization is a decision to use Genesis; it is not evidence that an existing
|
|
@@ -30,6 +36,11 @@ custom agent tool counts as existing content. Ignored files also count, except
|
|
|
30
36
|
installed `node_modules` dependencies. Source recognition does not depend on a
|
|
31
37
|
language detector or an AI model.
|
|
32
38
|
|
|
39
|
+
OpenCode's ignored plugin-install metadata also leaves a blank project eligible:
|
|
40
|
+
its exact generated `.opencode/.gitignore`, plugin-only package manifest and
|
|
41
|
+
matching npm lockfile. These files are read, not deleted or repaired. Tracked or
|
|
42
|
+
customized manifests and other OpenCode files still count as existing content.
|
|
43
|
+
|
|
33
44
|
Opening a session does not run verification, rebuild the source index, install
|
|
34
45
|
dependencies, prepare a database, or start the application. The inspection is
|
|
35
46
|
repeated as a small read when opening/resuming a session and when a host refreshes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genesis-compiler",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "An agent-independent prompt, multi-language code-index, cleanup, and verification companion with project agent guidance.",
|
|
6
6
|
"repository": {
|
|
@@ -47,14 +47,16 @@
|
|
|
47
47
|
"docs/parsers.md"
|
|
48
48
|
],
|
|
49
49
|
"scripts": {
|
|
50
|
-
"test": "node --test test/*.test.js"
|
|
50
|
+
"test": "node --test --test-concurrency=1 test/*.test.js"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@ast-grep/napi": "^0.45.1",
|
|
54
54
|
"mdast-util-from-markdown": "^2.0.3",
|
|
55
|
+
"minimatch": "10.2.6",
|
|
55
56
|
"yaml": "^2.9.0"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
|
-
"@ast-grep/lang-python": "0.0.6"
|
|
59
|
+
"@ast-grep/lang-python": "0.0.6",
|
|
60
|
+
"genesis-stack": "file:packages/genesis-stack"
|
|
59
61
|
}
|
|
60
62
|
}
|
package/prompts/describe.txt
CHANGED
|
@@ -15,6 +15,11 @@ result. Identify anything not inspected or genuinely uncertain.
|
|
|
15
15
|
Edit only `genesis/blueprint.md`, `genesis/subsystems.md`, and explanatory Markdown below
|
|
16
16
|
`genesis/program/`. Summarize every explanatory file created, updated, or
|
|
17
17
|
removed.
|
|
18
|
+
Write useful subsystem responsibility explanations covering purpose, scope,
|
|
19
|
+
important parts and collaboration. Preserve substantive existing prose rather
|
|
20
|
+
than replacing it with short visual captions. Reconcile changed directory owners,
|
|
21
|
+
Program membership and required main-source anchors together; validate the map
|
|
22
|
+
and Program after regeneration. Consumers reuse this canonical list.
|
|
18
23
|
|
|
19
24
|
Maintain `genesis/subsystems.md` in the same implementation turn when a change
|
|
20
25
|
adds, removes, or changes a subsystem responsibility, Program membership, or
|
package/prompts/program.txt
CHANGED
|
@@ -9,7 +9,15 @@ Delete stale or duplicate modules and do not edit code, tests, configuration,
|
|
|
9
9
|
Blueprint, Stack, or `.genesis/`.
|
|
10
10
|
|
|
11
11
|
When finished, summarize Program files created, updated, or removed and report
|
|
12
|
-
genuine ambiguity.
|
|
12
|
+
genuine ambiguity. Every module must designate exactly one main source with
|
|
13
|
+
`- Main: \`path/to/source\`` in Sources. Inspect its implementation and choose a
|
|
14
|
+
reading anchor in its owning subsystem; do not guess from source-list order.
|
|
15
|
+
Treat each subsystem responsibility as the full useful explanation, not a
|
|
16
|
+
one-line map caption. Preserve substantive descriptions. If boundaries change,
|
|
17
|
+
reconcile the prose, Program assignments and required main anchors with the new
|
|
18
|
+
directory owners, including nested roots. Consumers read the current canonical
|
|
19
|
+
map; there is no separate viewer-owned list to maintain. Validate both subsystem
|
|
20
|
+
declarations and Program membership before reporting completion.
|
|
13
21
|
|
|
14
22
|
Maintain `genesis/subsystems.md` in the same implementation turn when a change
|
|
15
23
|
adds, removes, or changes a subsystem responsibility, Program membership, or
|
|
@@ -43,13 +43,22 @@ One short explanation of why the boundary exists.
|
|
|
43
43
|
|
|
44
44
|
## Sources
|
|
45
45
|
|
|
46
|
-
- `exact/authored/source/path`
|
|
46
|
+
- Main: `exact/authored/main/source/path`
|
|
47
|
+
- `exact/authored/supporting/source/path`
|
|
47
48
|
|
|
48
49
|
## Public contract
|
|
49
50
|
|
|
50
51
|
Meaningful inputs, outputs, effects, failures, and guarantees.
|
|
51
52
|
```
|
|
52
53
|
|
|
54
|
+
Every module requires exactly one `- Main:` source bullet. Choose the main
|
|
55
|
+
source explicitly as the place to start reading the implementation; it need not
|
|
56
|
+
be an executable entry point. It must belong to the module's owning subsystem,
|
|
57
|
+
respecting more specific nested directory owners. Other Sources may cite shared
|
|
58
|
+
libraries in other subsystems. Never infer the main file from source-list order,
|
|
59
|
+
imports or file naming. When reconciling older modules, inspect the source and
|
|
60
|
+
mark the anchor explicitly; indexing does not assign one for you.
|
|
61
|
+
|
|
53
62
|
An optional `## Implementation map` may name only private helpers or seams that
|
|
54
63
|
materially help a future agent change, trace, or debug the operation. It is
|
|
55
64
|
informational, not a public guarantee. Tests are evidence and never
|
|
@@ -82,6 +91,9 @@ Use this exact version-zero grammar:
|
|
|
82
91
|
|
|
83
92
|
Owns reusable form rendering, validation, and submission capture.
|
|
84
93
|
|
|
94
|
+
### Source
|
|
95
|
+
- Directory `packages/forms`
|
|
96
|
+
|
|
85
97
|
### Program
|
|
86
98
|
- `genesis/program/forms/render.md`
|
|
87
99
|
|
|
@@ -93,10 +105,35 @@ Owns reusable form rendering, validation, and submission capture.
|
|
|
93
105
|
```
|
|
94
106
|
|
|
95
107
|
Use one stable lowercase hyphenated id and a human title per subsystem. Each
|
|
96
|
-
entry needs responsibility prose and
|
|
108
|
+
entry needs responsibility prose and the Program, Data owned and Data used sections.
|
|
109
|
+
Responsibility prose is the subsystem's full explanation, not a map caption.
|
|
110
|
+
Explain its purpose, scope, important parts and collaboration with other owners.
|
|
111
|
+
Preserve substantive existing descriptions; do not replace them with one-line
|
|
112
|
+
labels to suit a visualisation. A consumer can shorten the displayed preview.
|
|
113
|
+
Simple support directories may need only a short explanation.
|
|
114
|
+
The required Source section contains exactly one `- Directory` entry with a backticked path: a
|
|
115
|
+
canonical project-relative directory, with `.` allowed for the project root.
|
|
116
|
+
Every subsystem has one directory root; never declare dispersed or multiple
|
|
117
|
+
roots. Stack parent-directory rules discover default subsystem roots but are not
|
|
118
|
+
exhaustive. An agent may recognise and declare another root elsewhere from
|
|
119
|
+
actual responsibility and source evidence. The authored declaration persists
|
|
120
|
+
through ordinary indexing. The authored map is the canonical owner list once ready; Stack roots are adoption
|
|
121
|
+
hints and never create additional owners or split an authored boundary. Cover every
|
|
122
|
+
indexed source, using a project-root owner for remaining support files if needed. UI components and Engine code may
|
|
123
|
+
share one root; UI projection does not change ownership. Empty
|
|
97
124
|
sections say `- Nothing.`; a new empty map contains only `# Subsystems` and
|
|
98
|
-
`- Nothing.`. Every Program module belongs to exactly one subsystem.
|
|
99
|
-
|
|
125
|
+
`- Nothing.`. Every Program module belongs to exactly one subsystem. Program directory names do not assign ownership. Sources may cite another
|
|
126
|
+
subsystem’s libraries without giving the operation or implementation two owners.
|
|
127
|
+
Do not use globs, duplicate operations or duplicate table owners.
|
|
128
|
+
|
|
129
|
+
When regenerating or deliberately splitting the map, reconcile the explanations
|
|
130
|
+
with the new directory owners. Transfer the applicable responsibility prose and
|
|
131
|
+
explicit Program assignments together, preserving stable ids where the boundary
|
|
132
|
+
is unchanged. Recheck every Program's required main source against its current
|
|
133
|
+
owner, including nested owners; library references retain their own ownership.
|
|
134
|
+
Use the current declarations rather than an earlier consumer's hardcoded names,
|
|
135
|
+
directory layout or cached list. Inspect subsystems and Program and resolve
|
|
136
|
+
missing/duplicate membership and invalid main anchors before claiming completion.
|
|
100
137
|
|
|
101
138
|
A table identity is three separate exact backticked values: the declared Stack
|
|
102
139
|
resource id, schema, and table. `default` means the resource's default schema;
|
|
@@ -164,8 +164,8 @@ an unrun check passed or that passing checks prove the whole product.
|
|
|
164
164
|
Maintain `genesis/subsystems.md` in the same implementation turn when a change
|
|
165
165
|
adds, removes, or changes a subsystem responsibility, Program membership, or
|
|
166
166
|
data ownership/use. Read the map before changing related code. Reuse existing
|
|
167
|
-
subsystems unless a distinct responsibility is evidenced.
|
|
168
|
-
|
|
167
|
+
subsystems unless a distinct responsibility is evidenced. Require one Source Directory per subsystem and declare each Program module
|
|
168
|
+
exactly once; Program folder names do not determine membership. Keep table references
|
|
169
169
|
grounded in schema/migrations; Genesis does not inspect databases. Private
|
|
170
170
|
helper changes need no map edit when these associations remain unchanged.
|
|
171
171
|
|
package/src/cli.js
CHANGED
|
@@ -426,8 +426,8 @@ function writeResult(command, result) {
|
|
|
426
426
|
line(process.stdout, `${command}: ${result.status}`);
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
-
async function
|
|
430
|
-
if (!environment[HOST_CONTEXT_RESOLVER_ENV]) return
|
|
429
|
+
async function hookSessionInput(environment = process.env) {
|
|
430
|
+
if (!environment[HOST_CONTEXT_RESOLVER_ENV]) return {};
|
|
431
431
|
let source = environment[HOST_CONTEXT_INPUT_ENV];
|
|
432
432
|
if (source === undefined && !process.stdin.isTTY) {
|
|
433
433
|
const chunks = [];
|
|
@@ -441,7 +441,7 @@ async function hookSessionId(environment = process.env) {
|
|
|
441
441
|
}
|
|
442
442
|
source = Buffer.concat(chunks).toString('utf8');
|
|
443
443
|
}
|
|
444
|
-
if (!String(source || '').trim()) return
|
|
444
|
+
if (!String(source || '').trim()) return {};
|
|
445
445
|
let input;
|
|
446
446
|
try {
|
|
447
447
|
input = JSON.parse(source);
|
|
@@ -449,7 +449,15 @@ async function hookSessionId(environment = process.env) {
|
|
|
449
449
|
fail('HOST_CONTEXT_INPUT_INVALID', 'The host hook input must contain JSON.');
|
|
450
450
|
}
|
|
451
451
|
const sessionId = input?.sessionId ?? input?.session_id;
|
|
452
|
-
|
|
452
|
+
const parentSessionIds = input?.parentSessionIds ?? [];
|
|
453
|
+
if (!Array.isArray(parentSessionIds) || parentSessionIds.length > 32 ||
|
|
454
|
+
parentSessionIds.some(id => typeof id !== 'string' || !id.trim())) {
|
|
455
|
+
fail('HOST_CONTEXT_INPUT_INVALID', 'Native parent session ids must be a bounded list of strings.');
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
providerSessionId: typeof sessionId === 'string' && sessionId.trim() ? sessionId.trim() : null,
|
|
459
|
+
...(parentSessionIds.length ? { parentSessionIds } : {}),
|
|
460
|
+
};
|
|
453
461
|
}
|
|
454
462
|
|
|
455
463
|
async function cliStackPackages(projectRoot, supplied) {
|
|
@@ -574,19 +582,24 @@ async function execute({ command, operands, options }, { signal } = {}) {
|
|
|
574
582
|
if (scope === 'session' && process.env[SESSION_CONTEXT_INSTALLED_ENV] === '1') {
|
|
575
583
|
return { kind: 'session', status: 'ready', output: '' };
|
|
576
584
|
}
|
|
577
|
-
const
|
|
585
|
+
const nativeSession = await hookSessionInput();
|
|
578
586
|
const hostContext = await resolveConfiguredHostContext({
|
|
579
587
|
projectRoot,
|
|
580
|
-
|
|
588
|
+
...nativeSession,
|
|
581
589
|
scope,
|
|
582
590
|
});
|
|
591
|
+
// Ownership is resolved for this native conversation, not for the project.
|
|
592
|
+
// A host-owned delivery already contains everything its runtime requires.
|
|
593
|
+
if (hostContext?.kind === 'delivery') {
|
|
594
|
+
return { kind: 'session', status: 'ready', output: hostContext.text };
|
|
595
|
+
}
|
|
583
596
|
if (scope === 'turn') {
|
|
584
597
|
return hostContext === null
|
|
585
598
|
? { kind: 'turn', status: 'ready', output: '' }
|
|
586
599
|
: {
|
|
587
600
|
kind: 'turn',
|
|
588
601
|
...await projectTurnContext({
|
|
589
|
-
hostDriver: () => hostContext,
|
|
602
|
+
hostDriver: () => hostContext.text,
|
|
590
603
|
hostDriverInput: { scope: 'turn' },
|
|
591
604
|
}),
|
|
592
605
|
};
|
|
@@ -595,7 +608,7 @@ async function execute({ command, operands, options }, { signal } = {}) {
|
|
|
595
608
|
kind: 'session',
|
|
596
609
|
...await projectSessionContext({
|
|
597
610
|
...(hostContext === null ? {} : {
|
|
598
|
-
hostDriver: () => hostContext,
|
|
611
|
+
hostDriver: () => hostContext.text,
|
|
599
612
|
hostDriverInput: { scope: 'session' },
|
|
600
613
|
}),
|
|
601
614
|
projectRoot,
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { GenesisError } from './errors.js';
|
|
3
|
+
import { sha256, stableJson } from './utils.js';
|
|
4
|
+
|
|
5
|
+
export const ARCHITECTURE_OBSERVATIONS_SCHEMA = 'genesis.architecture-observations.v1';
|
|
6
|
+
const collections = ['pages', 'registrations', 'components', 'componentUses', 'entryOperations', 'operationUses'];
|
|
7
|
+
const optionalCollections = ['imports'];
|
|
8
|
+
const states = ['complete', 'partial', 'unsupported', 'unconfigured'];
|
|
9
|
+
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
10
|
+
const text = value => typeof value === 'string' && value.length > 0 && !/[\0\r\n]/u.test(value);
|
|
11
|
+
const canonical = value => text(value) && value !== '.' && !/[\\]/u.test(value) && !path.posix.isAbsolute(value) && path.posix.normalize(value) === value && !value.split('/').includes('..');
|
|
12
|
+
const point = value => object(value) && Number.isSafeInteger(value.line) && value.line > 0 && Number.isSafeInteger(value.column) && value.column > 0;
|
|
13
|
+
|
|
14
|
+
function requireFact(condition, message) {
|
|
15
|
+
if (!condition) throw new GenesisError('ARCHITECTURE_OBSERVATIONS_INVALID', message);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// A finite, framework-independent JSON contract. Inspectors collect evidence;
|
|
19
|
+
// this owner validates identities and joins without executing application code.
|
|
20
|
+
export function validateArchitectureObservations(document, { files = [], packages = [] } = {}) {
|
|
21
|
+
requireFact(object(document) && document.schema === ARCHITECTURE_OBSERVATIONS_SCHEMA, `Expected ${ARCHITECTURE_OBSERVATIONS_SCHEMA}.`);
|
|
22
|
+
requireFact(text(document.adapter), 'An adapter identity is required.');
|
|
23
|
+
requireFact(Array.isArray(document.inputs), 'inputs must be an array.');
|
|
24
|
+
requireFact(object(document.capabilities), 'capabilities must be an object.');
|
|
25
|
+
for (const name of ['pages', 'components', 'entryOperations', 'operationUses']) requireFact(states.includes(document.capabilities[name]), `Missing or invalid ${name} capability.`);
|
|
26
|
+
if (document.capabilities.imports !== undefined) requireFact(states.includes(document.capabilities.imports), 'Invalid import coverage.');
|
|
27
|
+
requireFact(Array.isArray(document.diagnostics) && document.diagnostics.every(entry => object(entry) && text(entry.code) && text(entry.message)), 'diagnostics must contain code and message.');
|
|
28
|
+
const filesByPath = new Map(files.map(file => [file.path, file]));
|
|
29
|
+
const inputs = new Map();
|
|
30
|
+
for (const input of document.inputs) {
|
|
31
|
+
requireFact(object(input) && canonical(input.path) && text(input.hash), 'Every input needs a canonical project path and content hash.');
|
|
32
|
+
requireFact(!inputs.has(input.path), `Duplicate input ${input.path}.`);
|
|
33
|
+
const file = filesByPath.get(input.path);
|
|
34
|
+
requireFact(file && file.hash === input.hash, `Input ${input.path} does not match the indexed source. Refresh against the same snapshot.`);
|
|
35
|
+
inputs.set(input.path, input);
|
|
36
|
+
}
|
|
37
|
+
const ids = new Map();
|
|
38
|
+
for (const name of [...collections, ...optionalCollections]) {
|
|
39
|
+
requireFact(Array.isArray(document[name]) || (optionalCollections.includes(name) && document[name] === undefined), `${name} must be an array.`);
|
|
40
|
+
for (const record of document[name] || []) {
|
|
41
|
+
requireFact(object(record) && text(record.id) && !ids.has(record.id), `Invalid or duplicate ${name} identity.`);
|
|
42
|
+
requireFact(['resolved', 'unresolved', 'ambiguous'].includes(record.resolution), `${record.id} needs an explicit resolution.`);
|
|
43
|
+
requireFact(Array.isArray(record.evidence) && record.evidence.length > 0, `${record.id} needs source evidence.`);
|
|
44
|
+
for (const evidence of record.evidence) {
|
|
45
|
+
requireFact(object(evidence) && canonical(evidence.path) && inputs.get(evidence.path)?.hash === evidence.hash, `${record.id} evidence must reference a declared input hash.`);
|
|
46
|
+
requireFact(point(evidence.start) && point(evidence.end) && (evidence.end.line > evidence.start.line || (evidence.end.line === evidence.start.line && evidence.end.column > evidence.start.column)), `${record.id} has an impossible source range.`);
|
|
47
|
+
const lines = filesByPath.get(evidence.path)?.lines;
|
|
48
|
+
requireFact(!Number.isInteger(lines) || (evidence.start.line <= lines && (evidence.end.line <= lines || (evidence.end.line === lines + 1 && evidence.end.column === 1))), `${record.id} range exceeds its source.`);
|
|
49
|
+
}
|
|
50
|
+
ids.set(record.id, { name, record });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function reference(value, names, label) {
|
|
54
|
+
requireFact(ids.has(value) && names.includes(ids.get(value).name), `${label} refers to an unknown definition.`);
|
|
55
|
+
}
|
|
56
|
+
function source(value, label) { requireFact(inputs.has(value), `${label} must identify a declared input file.`); }
|
|
57
|
+
for (const use of document.imports || []) {
|
|
58
|
+
source(use.fromFile, use.id);
|
|
59
|
+
requireFact(text(use.specifier) && Array.isArray(use.names) && use.names.every(text) && typeof use.typeOnly === 'boolean', `${use.id} requires imported names, a specifier and type-only classification.`);
|
|
60
|
+
requireFact(use.evidence.some(evidence => evidence.path === use.fromFile), `${use.id} needs evidence in its importing file.`);
|
|
61
|
+
if (use.resolution === 'resolved') source(use.targetFile, use.id);
|
|
62
|
+
}
|
|
63
|
+
function operation(key, label) {
|
|
64
|
+
requireFact(object(key) && text(key.protocol) && text(key.scope) && text(key.operation), `${label} requires protocol, scope and operation identity.`);
|
|
65
|
+
requireFact(Object.values(key).every(text), `${label} fields must be non-empty strings.`);
|
|
66
|
+
if (key.protocol === 'http') requireFact(text(key.method) && key.method === key.method.toUpperCase(), `${label} requires an uppercase HTTP method.`);
|
|
67
|
+
}
|
|
68
|
+
for (const page of document.pages) {
|
|
69
|
+
requireFact(text(page.title) && text(page.surface) && text(page.kind), `${page.id} requires title, surface and page/screen/scene kind.`);
|
|
70
|
+
source(page.file, page.id);
|
|
71
|
+
if (page.parentId != null) reference(page.parentId, ['pages'], page.id);
|
|
72
|
+
}
|
|
73
|
+
const packagesById = new Map(packages.map(entry => [entry.id, entry]));
|
|
74
|
+
let packageIdentity;
|
|
75
|
+
for (const component of document.components) {
|
|
76
|
+
requireFact(text(component.title), `${component.id} requires a title.`);
|
|
77
|
+
if (component.origin?.kind === 'package') {
|
|
78
|
+
requireFact(component.file == null && packagesById.has(component.origin.packageId) && text(component.origin.importPath) && text(component.origin.symbol), `${component.id} requires a known package boundary and public import identity.`);
|
|
79
|
+
packageIdentity ??= sha256(stableJson(packages));
|
|
80
|
+
requireFact(document.packageIdentity === packageIdentity, 'Package-backed UI observations must match the inspected package snapshot.');
|
|
81
|
+
} else source(component.file, component.id);
|
|
82
|
+
}
|
|
83
|
+
for (const registration of document.registrations) {
|
|
84
|
+
reference(registration.pageId, ['pages'], registration.id);
|
|
85
|
+
requireFact(text(registration.surface) && text(registration.identity), `${registration.id} requires scoped registration identity.`);
|
|
86
|
+
}
|
|
87
|
+
for (const use of document.componentUses) {
|
|
88
|
+
reference(use.parentId, ['pages', 'components', 'componentUses'], use.id);
|
|
89
|
+
if (use.resolution === 'resolved') reference(use.componentId, ['components'], use.id);
|
|
90
|
+
}
|
|
91
|
+
for (const entry of document.entryOperations) {
|
|
92
|
+
operation(entry.key, entry.id);
|
|
93
|
+
if (entry.resolution === 'resolved') source(entry.controllerFile, entry.id);
|
|
94
|
+
if (entry.controllerSymbol != null) requireFact(text(entry.controllerSymbol), `${entry.id} has an invalid symbol.`);
|
|
95
|
+
}
|
|
96
|
+
for (const use of document.operationUses) {
|
|
97
|
+
reference(use.parentId, ['pages', 'components', 'componentUses'], use.id);
|
|
98
|
+
if (use.resolution === 'resolved') operation(use.key, use.id);
|
|
99
|
+
}
|
|
100
|
+
// Parent graphs must be finite. Shared definitions are not duplicate owners.
|
|
101
|
+
for (const name of ['pages', 'componentUses']) for (const record of document[name]) {
|
|
102
|
+
const seen = new Set([record.id]);
|
|
103
|
+
let parent = ids.get(record.parentId)?.record;
|
|
104
|
+
while (parent) {
|
|
105
|
+
requireFact(!seen.has(parent.id), `Containment cycle at ${record.id}.`);
|
|
106
|
+
seen.add(parent.id); parent = ids.get(parent.parentId)?.record;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return document;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function connectArchitectureObservations(document, files, packages = []) {
|
|
113
|
+
validateArchitectureObservations(document, { files, packages });
|
|
114
|
+
const byPath = new Map(files.map(file => [file.path, file]));
|
|
115
|
+
const entries = new Map();
|
|
116
|
+
for (const entry of document.entryOperations.filter(record => record.resolution === 'resolved')) {
|
|
117
|
+
const key = stableJson(entry.key);
|
|
118
|
+
if (!entries.has(key)) entries.set(key, []);
|
|
119
|
+
entries.get(key).push(entry);
|
|
120
|
+
}
|
|
121
|
+
const connections = document.operationUses.map(use => {
|
|
122
|
+
const matches = use.resolution === 'resolved' ? entries.get(stableJson(use.key)) || [] : [];
|
|
123
|
+
const entry = matches.length === 1 ? matches[0] : null;
|
|
124
|
+
return { id: `connection:${use.id}`, useId: use.id, parentId: use.parentId, key: use.key || null, resolution: entry ? 'resolved' : matches.length > 1 ? 'ambiguous' : 'unresolved', entryId: entry?.id || null, controllerFileId: entry ? byPath.get(entry.controllerFile).id : null, controllerSymbol: entry?.controllerSymbol || null, evidence: [...use.evidence, ...(entry?.evidence || [])] };
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
...document, connections,
|
|
128
|
+
components: document.components.map(component => ({ ...component, fileId: byPath.get(component.file)?.id || null, subsystemId: component.origin?.kind === 'package' ? packages.find(entry => entry.id === component.origin.packageId).subsystemId : byPath.get(component.file).subsystemId })),
|
|
129
|
+
pages: document.pages.map(page => ({ ...page, fileId: byPath.get(page.file).id, subsystemId: byPath.get(page.file).subsystemId })),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function mergeArchitectureObservations(documents, { files = [], packages = [] } = {}) {
|
|
134
|
+
requireFact(Array.isArray(documents) && documents.length > 0, 'At least one observation document is required.');
|
|
135
|
+
const merged = { schema: ARCHITECTURE_OBSERVATIONS_SCHEMA, adapter: '', inputs: [], capabilities: {}, diagnostics: [], ...Object.fromEntries([...collections, ...optionalCollections].map(name => [name, []])) };
|
|
136
|
+
const adapters = new Set();
|
|
137
|
+
for (const document of documents) {
|
|
138
|
+
requireFact(document?.schema === ARCHITECTURE_OBSERVATIONS_SCHEMA && text(document.adapter) && !adapters.has(document.adapter), 'Every inspector needs a unique adapter identity and the observation schema.');
|
|
139
|
+
adapters.add(document.adapter);
|
|
140
|
+
if (document.packageIdentity != null) {
|
|
141
|
+
requireFact(!merged.packageIdentity || merged.packageIdentity === document.packageIdentity, 'Inspectors disagree about the package snapshot.');
|
|
142
|
+
merged.packageIdentity = document.packageIdentity;
|
|
143
|
+
}
|
|
144
|
+
for (const name of ['inputs', ...collections, ...optionalCollections]) {
|
|
145
|
+
requireFact(Array.isArray(document[name]) || (optionalCollections.includes(name) && document[name] === undefined), `${name} must be an array.`);
|
|
146
|
+
const byId = new Map(merged[name].map(record => [name === 'inputs' ? record.path : record.id, record]));
|
|
147
|
+
for (const record of document[name] || []) {
|
|
148
|
+
requireFact(object(record), `Invalid ${name} record.`);
|
|
149
|
+
const id = name === 'inputs' ? record.path : record.id;
|
|
150
|
+
const previous = byId.get(id);
|
|
151
|
+
requireFact(!previous || stableJson(previous) === stableJson(record), `Conflicting ${name} identity ${id}.`);
|
|
152
|
+
if (!previous) { merged[name].push(record); byId.set(id, record); }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
requireFact(Array.isArray(document.diagnostics), 'diagnostics must be an array.');
|
|
156
|
+
merged.diagnostics.push(...document.diagnostics);
|
|
157
|
+
}
|
|
158
|
+
merged.adapter = [...adapters].sort().join('+');
|
|
159
|
+
for (const capability of ['pages', 'components', 'entryOperations', 'operationUses']) {
|
|
160
|
+
const values = documents.map(document => document.capabilities?.[capability]);
|
|
161
|
+
requireFact(values.every(value => states.includes(value)), `Invalid ${capability} coverage.`);
|
|
162
|
+
merged.capabilities[capability] = new Set(values).size === 1 ? values[0] : 'partial';
|
|
163
|
+
}
|
|
164
|
+
const importCoverage = documents.map(document => document.capabilities?.imports || 'unconfigured');
|
|
165
|
+
merged.capabilities.imports = new Set(importCoverage).size === 1 ? importCoverage[0] : 'partial';
|
|
166
|
+
return validateArchitectureObservations(merged, { files, packages });
|
|
167
|
+
}
|