genesis-compiler 1.2.29 → 1.3.1
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 +70 -3
- package/docs/prompt-integration.md +93 -0
- package/package.json +1 -1
- package/plugins/genesis/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/project-guidance.js +41 -5
- package/skills/genesis-deslop/SKILL.md +17 -6
- package/skills/genesis-project/SKILL.md +17 -6
- package/src/cli.js +124 -7
- package/src/index/check.js +10 -0
- package/src/index/codex-hooks.js +16 -1
- package/src/index/collaboration.js +371 -0
- package/src/index/contracts.js +3 -0
- package/src/index/host-context-resolver.js +73 -0
- package/src/index/init.js +4 -0
- package/src/index/migration.js +2 -0
- package/src/index/paths.js +1 -0
- package/src/index/project-format.js +3 -1
- package/src/index/prompt.js +74 -21
- package/src/index/session-context.js +115 -25
- package/src/index.js +27 -2
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ The useful mental model is:
|
|
|
24
24
|
genesis/
|
|
25
25
|
version project-file format version used for deterministic migrations
|
|
26
26
|
blueprint.md non-technical product intent
|
|
27
|
+
collaboration.md project-wide communication choices and authored requirements
|
|
27
28
|
engineering.md selected engineering profile and project-specific requirements
|
|
28
29
|
stack.md selected components, resources, verification, and project-owned operation contracts
|
|
29
30
|
stack/ optional per-component Description, Guidance, Adoption, Post-change, and Deslop customization
|
|
@@ -237,6 +238,60 @@ Project-scoped options follow the command, for example
|
|
|
237
238
|
a host or script needs the normalized result. Run `genesis --help` for the
|
|
238
239
|
compact CLI synopsis.
|
|
239
240
|
|
|
241
|
+
## Collaboration approach
|
|
242
|
+
|
|
243
|
+
Genesis keeps project-wide communication guidance in ordinary source rather
|
|
244
|
+
than in a host-private policy or a repeated user-message prefix:
|
|
245
|
+
|
|
246
|
+
```markdown
|
|
247
|
+
# Collaboration approach
|
|
248
|
+
|
|
249
|
+
## Tone
|
|
250
|
+
|
|
251
|
+
- `encouraging`
|
|
252
|
+
|
|
253
|
+
## Response length
|
|
254
|
+
|
|
255
|
+
- `concise`
|
|
256
|
+
|
|
257
|
+
## Assumed experience
|
|
258
|
+
|
|
259
|
+
- `comfortable`
|
|
260
|
+
|
|
261
|
+
## Explanation style
|
|
262
|
+
|
|
263
|
+
- `concise`
|
|
264
|
+
|
|
265
|
+
## Project requirements
|
|
266
|
+
|
|
267
|
+
- Nothing.
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Inspect or change it without parsing Markdown yourself:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
genesis collaboration show
|
|
274
|
+
genesis collaboration set direct balanced expert conclusions "Be candid."
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Genesis owns the supported choices and their concise expansions. Authored
|
|
278
|
+
project requirements receive one fixed framing sentence and are otherwise
|
|
279
|
+
preserved. They are omitted when the source says `- Nothing.`.
|
|
280
|
+
|
|
281
|
+
Persistent hosts should install `projectSessionContext()` once when a provider
|
|
282
|
+
conversation is created or refreshed. They may then generate task prompts with
|
|
283
|
+
`sessionContextInstalled: true`, preventing the stable Collaboration and
|
|
284
|
+
Engineering guidance from being repeated in each task. A host can supply one
|
|
285
|
+
explicit session/turn driver; Genesis performs no driver discovery and labels
|
|
286
|
+
turn output untrusted. If the project-local Codex or OpenCode adapter runs in a
|
|
287
|
+
separate process, the host may set `GENESIS_HOST_CONTEXT_RESOLVER` to one
|
|
288
|
+
executable and optionally supply opaque JSON in
|
|
289
|
+
`GENESIS_HOST_CONTEXT_RESOLVER_DATA`. Session and turn hooks pass only the
|
|
290
|
+
requested lane, native provider session id, and that configured data to the
|
|
291
|
+
executable. Session output is composed into stable context; a nonempty turn
|
|
292
|
+
result is capped at 512 bytes, labeled untrusted, and kept separate from the
|
|
293
|
+
authored user message. This is a single process bridge, not a plugin registry.
|
|
294
|
+
|
|
240
295
|
## Engineering approach
|
|
241
296
|
|
|
242
297
|
Every profile inherits one universal rule: implementation must remain easy to
|
|
@@ -780,7 +835,8 @@ genesis migrate
|
|
|
780
835
|
|
|
781
836
|
Migration is deterministic and forward-only. The format 2 migration snapshots
|
|
782
837
|
the previously effective Stack operations into project-owned sections before
|
|
783
|
-
removing component-runtime fallback
|
|
838
|
+
removing component-runtime fallback; format 3 adds the portable Collaboration
|
|
839
|
+
declaration. Migration also synchronizes managed skills
|
|
784
840
|
and hooks, regenerates derived indexes, and then returns a fresh `check`
|
|
785
841
|
result. A newer project is never downgraded. Every project-scoped CLI
|
|
786
842
|
invocation warns when the recorded format does not match the running CLI, and
|
|
@@ -797,6 +853,7 @@ import {
|
|
|
797
853
|
getContext,
|
|
798
854
|
indexCodebase,
|
|
799
855
|
initialize,
|
|
856
|
+
inspectCollaboration,
|
|
800
857
|
inspectEngineering,
|
|
801
858
|
inspectEnvironment,
|
|
802
859
|
inspectStackSection,
|
|
@@ -804,6 +861,9 @@ import {
|
|
|
804
861
|
listEngineeringProfiles,
|
|
805
862
|
listStackPieces,
|
|
806
863
|
migrate,
|
|
864
|
+
projectSessionContext,
|
|
865
|
+
projectTurnContext,
|
|
866
|
+
setCollaboration,
|
|
807
867
|
setEngineeringProfile,
|
|
808
868
|
verify,
|
|
809
869
|
} from 'genesis-compiler';
|
|
@@ -818,6 +878,12 @@ codebase. `installCodex()` installs the optional global discovery plugin.
|
|
|
818
878
|
`listEngineeringProfiles()`, `inspectEngineering()`, and
|
|
819
879
|
`setEngineeringProfile()` expose the same built-in catalog and portable
|
|
820
880
|
project selection used by the CLI and hosts.
|
|
881
|
+
`inspectCollaboration()` and `setCollaboration()` expose the portable
|
|
882
|
+
communication declaration and Genesis-owned choice expansions.
|
|
883
|
+
`projectSessionContext()` composes stable project guidance with one explicitly
|
|
884
|
+
supplied host-driver result. `projectTurnContext()` invokes that same optional
|
|
885
|
+
driver for the separate untrusted turn lane without accepting user-message
|
|
886
|
+
text.
|
|
821
887
|
`getContext()` resolves source paths to the Program modules that cite them,
|
|
822
888
|
the functions already declared there, selected Stack guidance, available Agent
|
|
823
889
|
Skills, and verification commands. `indexCodebase()` regenerates or returns the
|
|
@@ -829,8 +895,9 @@ projection paths without returning any supplied environment value.
|
|
|
829
895
|
interpreting or executing its contents.
|
|
830
896
|
|
|
831
897
|
Normalized results identify their stable public contract in the `contract`
|
|
832
|
-
field: `genesis.
|
|
833
|
-
`genesis.
|
|
898
|
+
field: `genesis.collaboration.v1`, `genesis.engineering.v1`,
|
|
899
|
+
`genesis.environment.v2`, `genesis.session-context.v1`,
|
|
900
|
+
`genesis.stack-section.v1`, `genesis.turn-context.v1`, or
|
|
834
901
|
`genesis.verification.v1`. A consumer defines any schema embedded inside the
|
|
835
902
|
opaque section body.
|
|
836
903
|
|
|
@@ -15,6 +15,81 @@ const work = await generatePrompt({
|
|
|
15
15
|
await currentAgent.send(work.prompt);
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
A persistent host installs stable context separately and keeps ordinary user
|
|
19
|
+
text unchanged:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import {
|
|
23
|
+
generatePrompt,
|
|
24
|
+
projectSessionContext,
|
|
25
|
+
projectTurnContext,
|
|
26
|
+
} from 'genesis-compiler';
|
|
27
|
+
|
|
28
|
+
const sessionContext = await projectSessionContext({
|
|
29
|
+
projectRoot,
|
|
30
|
+
hostDriver,
|
|
31
|
+
hostDriverInput: {
|
|
32
|
+
scope: 'session',
|
|
33
|
+
conversationKind: 'main',
|
|
34
|
+
session: { managedPreview: true },
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
await provider.installTrustedSessionContext(sessionContext.output);
|
|
38
|
+
|
|
39
|
+
const task = await generatePrompt({
|
|
40
|
+
projectRoot,
|
|
41
|
+
task: 'work',
|
|
42
|
+
request: userMessage,
|
|
43
|
+
sessionContextInstalled: true,
|
|
44
|
+
});
|
|
45
|
+
const turnContext = await projectTurnContext({
|
|
46
|
+
hostDriver,
|
|
47
|
+
hostDriverInput: {
|
|
48
|
+
scope: 'turn',
|
|
49
|
+
actor: { preferredAddressName: 'Merc' },
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
await provider.sendUserTurn({
|
|
53
|
+
text: task.prompt,
|
|
54
|
+
untrustedContext: turnContext.output,
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The provider method names above are illustrative. Genesis supplies semantic
|
|
59
|
+
session and untrusted-turn results; the host's provider adapter owns their
|
|
60
|
+
native placement. The driver never receives or rewrites `userMessage`, and
|
|
61
|
+
Genesis supports only the one explicitly supplied driver—there is no discovery
|
|
62
|
+
or plugin registry.
|
|
63
|
+
|
|
64
|
+
When the project-local Codex or OpenCode adapter runs in a separate process,
|
|
65
|
+
the host can configure that same one-driver boundary without adding another
|
|
66
|
+
prompt compositor:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
GENESIS_HOST_CONTEXT_RESOLVER=/path/to/host-context-resolver
|
|
70
|
+
GENESIS_HOST_CONTEXT_RESOLVER_DATA={"registry":"/runtime/host-sessions.json"}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
For `genesis hook session`, Genesis invokes that one executable with bounded
|
|
74
|
+
JSON on stdin:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"scope": "session",
|
|
79
|
+
"providerSessionId": "native-session-id",
|
|
80
|
+
"data": { "registry": "/runtime/host-sessions.json" }
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
For `genesis hook turn`, the input has the same shape with `scope: "turn"`.
|
|
85
|
+
The executable returns only the requested host contribution as text. Codex's
|
|
86
|
+
hook input and OpenCode's transform callbacks supply the native session id;
|
|
87
|
+
Genesis extracts that id but does not understand the host registry or the
|
|
88
|
+
provider's prompt-placement rules. The Codex prompt hook and OpenCode message
|
|
89
|
+
transform place a nonempty turn result separately from authored user text. If
|
|
90
|
+
no resolver is configured, both turn paths are empty and the adapters render
|
|
91
|
+
ordinary Genesis session context exactly as before.
|
|
92
|
+
|
|
18
93
|
Tasks are `start`, `adopt`, `work`, `deslop`, `program`, `blueprint`, `describe`, and `review`.
|
|
19
94
|
`start` is the host-independent first conversation: it classifies an initialized
|
|
20
95
|
project from its selected Stack and Git-visible project paths without building
|
|
@@ -48,6 +123,9 @@ Genesis keeps each instruction at one useful level:
|
|
|
48
123
|
structural Indexers, and an optional authoritative Agent Skill source.
|
|
49
124
|
- `genesis/blueprint.md`, `genesis/program/`, and `genesis/stack.md` are the
|
|
50
125
|
project's own intent, explanation, and selected technical composition.
|
|
126
|
+
- `genesis/collaboration.md` selects Genesis-owned tone, response-length,
|
|
127
|
+
assumed-experience, and explanation-style instructions plus optional authored
|
|
128
|
+
project requirements.
|
|
51
129
|
- `profiles/engineering/*.md` owns the installed versioned engineering
|
|
52
130
|
profiles. `genesis/engineering.md` selects one profile and records explicit
|
|
53
131
|
project requirements. A universal complexity gate is composed ahead of the
|
|
@@ -97,6 +175,21 @@ requests another model turn. Selected `Post-change` contributions are included
|
|
|
97
175
|
in the original implementation prompt, whose project skill also keeps
|
|
98
176
|
intentional Blueprint and affected Program changes aligned.
|
|
99
177
|
|
|
178
|
+
An out-of-process host may configure the single resolver described above. Each
|
|
179
|
+
adapter passes only its native session id to Genesis; Genesis sends that id,
|
|
180
|
+
the requested lane, and explicitly configured opaque data to the resolver.
|
|
181
|
+
Session output is composed with the stable guide; turn output remains a small
|
|
182
|
+
untrusted item beside a selected real user turn. This is one process bridge,
|
|
183
|
+
not a driver or plugin registry.
|
|
184
|
+
|
|
185
|
+
Hosts that own a persistent provider conversation can call
|
|
186
|
+
`projectSessionContext()` directly and pass at most one explicit host driver.
|
|
187
|
+
The complete result includes Genesis Collaboration and Engineering guidance and
|
|
188
|
+
has a stable identity. `projectTurnContext()` calls that driver only for a real
|
|
189
|
+
turn selected by the host and labels its separate result untrusted. Hosts use
|
|
190
|
+
`sessionContextInstalled: true` for task prompts only after installing the
|
|
191
|
+
session result; the default command-line prompt remains self-contained.
|
|
192
|
+
|
|
100
193
|
The operating guide dynamically lists only the component ids in the currently
|
|
101
194
|
available Stack catalogs. The naked CLI uses an explicitly installed
|
|
102
195
|
first-party `genesis-stack` package as its initial catalog without exposing the
|
package/package.json
CHANGED
|
@@ -5,12 +5,18 @@ import { promisify } from "node:util";
|
|
|
5
5
|
|
|
6
6
|
const execute = promisify(execFile);
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const HOST_CONTEXT_INPUT_ENV = "GENESIS_HOST_CONTEXT_INPUT";
|
|
9
|
+
|
|
10
|
+
async function renderContext(projectRoot, sessionId, scope) {
|
|
9
11
|
const { stdout } = await execute(
|
|
10
12
|
"genesis",
|
|
11
|
-
["hook",
|
|
13
|
+
["hook", scope, "--project-root", projectRoot],
|
|
12
14
|
{
|
|
13
15
|
cwd: projectRoot,
|
|
16
|
+
env: {
|
|
17
|
+
...process.env,
|
|
18
|
+
[HOST_CONTEXT_INPUT_ENV]: JSON.stringify({ sessionId })
|
|
19
|
+
},
|
|
14
20
|
maxBuffer: 256 * 1024,
|
|
15
21
|
timeout: 5_000
|
|
16
22
|
}
|
|
@@ -25,13 +31,13 @@ function eventSessionId(event = {}) {
|
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
export const GenesisProjectGuidance = async ({ directory, worktree } = {}) => {
|
|
28
|
-
const projectRoot = path.resolve(
|
|
34
|
+
const projectRoot = path.resolve(directory || worktree || process.cwd());
|
|
29
35
|
const contexts = new Map();
|
|
30
36
|
|
|
31
37
|
function contextForSession(sessionId) {
|
|
32
38
|
let context = contexts.get(sessionId);
|
|
33
39
|
if (!context) {
|
|
34
|
-
context =
|
|
40
|
+
context = renderContext(projectRoot, sessionId, "session");
|
|
35
41
|
contexts.set(sessionId, context);
|
|
36
42
|
context.catch(() => {
|
|
37
43
|
if (contexts.get(sessionId) === context) contexts.delete(sessionId);
|
|
@@ -50,7 +56,37 @@ export const GenesisProjectGuidance = async ({ directory, worktree } = {}) => {
|
|
|
50
56
|
const sessionId = String(input.sessionID || input.sessionId || "").trim();
|
|
51
57
|
if (!sessionId) return;
|
|
52
58
|
const context = await contextForSession(sessionId);
|
|
53
|
-
if (!output.system.includes(context)) output.system.push(context);
|
|
59
|
+
if (context && !output.system.includes(context)) output.system.push(context);
|
|
60
|
+
},
|
|
61
|
+
"experimental.chat.messages.transform": async (...hookArguments) => {
|
|
62
|
+
const output = hookArguments[1] || {};
|
|
63
|
+
const messages = Array.isArray(output.messages) ? output.messages : [];
|
|
64
|
+
const messageIndex = messages.findLastIndex((message) => message?.info?.role === "user");
|
|
65
|
+
if (messageIndex < 0) return;
|
|
66
|
+
const message = messages[messageIndex];
|
|
67
|
+
const sessionId = String(message.info?.sessionID || "").trim();
|
|
68
|
+
const messageId = String(message.info?.id || "").trim();
|
|
69
|
+
if (!sessionId || !messageId || !Array.isArray(message.parts)) return;
|
|
70
|
+
const context = await renderContext(projectRoot, sessionId, "turn");
|
|
71
|
+
if (!context || message.parts.some((part) => part?.synthetic === true && part?.text === context)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
output.messages = messages.map((candidate, index) => index === messageIndex
|
|
75
|
+
? {
|
|
76
|
+
...candidate,
|
|
77
|
+
parts: [
|
|
78
|
+
...candidate.parts,
|
|
79
|
+
{
|
|
80
|
+
id: `${messageId}_genesis_turn_context`,
|
|
81
|
+
messageID: messageId,
|
|
82
|
+
sessionID: sessionId,
|
|
83
|
+
synthetic: true,
|
|
84
|
+
text: context,
|
|
85
|
+
type: "text"
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
: candidate);
|
|
54
90
|
}
|
|
55
91
|
};
|
|
56
92
|
};
|
|
@@ -8,12 +8,23 @@ description: Deslop committed work through an explicit behavior-preserving clean
|
|
|
8
8
|
Review and simplify committed work without changing product behavior. Deslop is
|
|
9
9
|
explicit: never run it merely because implementation finished.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
Resolve the invocation before running the first Genesis operation. Do not use a
|
|
12
|
+
Genesis operation or `genesis --version` as an availability probe.
|
|
13
|
+
|
|
14
|
+
1. Read the repository-root `package.json`. Treat Genesis as project-pinned only
|
|
15
|
+
when that manifest's `name` is `genesis-compiler`, or its `dependencies`,
|
|
16
|
+
`devDependencies`, or `optionalDependencies` contain the exact
|
|
17
|
+
`genesis-compiler` key.
|
|
18
|
+
2. For a project-pinned repository, invoke every Genesis CLI operation with
|
|
19
|
+
`npm exec --no -- genesis <arguments>`. This runs without fetching another
|
|
20
|
+
package. If the declared package is not installed, report that the project's
|
|
21
|
+
dependencies are not prepared; do not fall back to another Genesis version.
|
|
22
|
+
3. Otherwise, do not try `npm exec`. Run `command -v genesis` (or the current
|
|
23
|
+
shell's equivalent `PATH` lookup). When it resolves an executable, invoke
|
|
24
|
+
`genesis <arguments>` directly.
|
|
25
|
+
4. When neither source is available, stop and report that Genesis is
|
|
26
|
+
unavailable. Never install or update Genesis merely to satisfy a workflow
|
|
27
|
+
instruction.
|
|
17
28
|
|
|
18
29
|
## Load the effective project instructions once
|
|
19
30
|
|
|
@@ -25,12 +25,23 @@ one exact investigation.
|
|
|
25
25
|
|
|
26
26
|
## Run Genesis commands
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
Resolve the invocation before running the first Genesis operation. Do not use a
|
|
29
|
+
Genesis operation or `genesis --version` as an availability probe.
|
|
30
|
+
|
|
31
|
+
1. Read the repository-root `package.json`. Treat Genesis as project-pinned only
|
|
32
|
+
when that manifest's `name` is `genesis-compiler`, or its `dependencies`,
|
|
33
|
+
`devDependencies`, or `optionalDependencies` contain the exact
|
|
34
|
+
`genesis-compiler` key.
|
|
35
|
+
2. For a project-pinned repository, invoke every Genesis CLI operation with
|
|
36
|
+
`npm exec --no -- genesis <arguments>`. This runs without fetching another
|
|
37
|
+
package. If the declared package is not installed, report that the project's
|
|
38
|
+
dependencies are not prepared; do not fall back to another Genesis version.
|
|
39
|
+
3. Otherwise, do not try `npm exec`. Run `command -v genesis` (or the current
|
|
40
|
+
shell's equivalent `PATH` lookup). When it resolves an executable, invoke
|
|
41
|
+
`genesis <arguments>` directly.
|
|
42
|
+
4. When neither source is available, stop and report that Genesis is
|
|
43
|
+
unavailable. Never install or update Genesis merely to satisfy a workflow
|
|
44
|
+
instruction.
|
|
34
45
|
|
|
35
46
|
## Resolve explicit technology choices
|
|
36
47
|
|
package/src/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getContext,
|
|
10
10
|
indexCodebase,
|
|
11
11
|
initialize,
|
|
12
|
+
inspectCollaboration,
|
|
12
13
|
inspectEngineering,
|
|
13
14
|
inspectEnvironment,
|
|
14
15
|
inspectStackSection,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
listEngineeringProfiles,
|
|
17
18
|
listStackPieces,
|
|
18
19
|
migrate,
|
|
20
|
+
setCollaboration,
|
|
19
21
|
setEngineeringProfile,
|
|
20
22
|
verify,
|
|
21
23
|
} from './index.js';
|
|
@@ -24,11 +26,17 @@ import {
|
|
|
24
26
|
} from './index/codex-hooks.js';
|
|
25
27
|
import { engineeringProfile, readEngineeringBaseline } from './index/engineering.js';
|
|
26
28
|
import { asDiagnostic, fail } from './index/errors.js';
|
|
29
|
+
import {
|
|
30
|
+
HOST_CONTEXT_INPUT_ENV,
|
|
31
|
+
HOST_CONTEXT_RESOLVER_ENV,
|
|
32
|
+
SESSION_CONTEXT_INSTALLED_ENV,
|
|
33
|
+
resolveConfiguredHostContext,
|
|
34
|
+
} from './index/host-context-resolver.js';
|
|
27
35
|
import {
|
|
28
36
|
inspectProjectFormat,
|
|
29
37
|
projectFormatDiagnostic,
|
|
30
38
|
} from './index/project-format.js';
|
|
31
|
-
import { projectSessionContext } from './index/session-context.js';
|
|
39
|
+
import { projectSessionContext, projectTurnContext } from './index/session-context.js';
|
|
32
40
|
import { installedFirstPartyStackPackages } from './index/stack-catalog.js';
|
|
33
41
|
import { readStack } from './index/stack.js';
|
|
34
42
|
|
|
@@ -36,6 +44,8 @@ const USAGE = `Usage:
|
|
|
36
44
|
genesis init
|
|
37
45
|
genesis adopt [product guidance...]
|
|
38
46
|
genesis codex install
|
|
47
|
+
genesis collaboration show
|
|
48
|
+
genesis collaboration set <tone> <response-length> <experience> <explanation-style> [project requirements...]
|
|
39
49
|
genesis engineering list
|
|
40
50
|
genesis engineering show [profile]
|
|
41
51
|
genesis engineering set <profile>
|
|
@@ -63,7 +73,7 @@ prompt to the agent you already use. Review all edits through the ordinary Git
|
|
|
63
73
|
diff, then run genesis verify for the Stack's concrete checks.
|
|
64
74
|
`;
|
|
65
75
|
|
|
66
|
-
const COMMANDS = new Set(['adopt', 'check', 'codex', 'context', 'engineering', 'hook', 'index', 'init', 'inspect', 'migrate', 'prompt', 'stack', 'verify']);
|
|
76
|
+
const COMMANDS = new Set(['adopt', 'check', 'codex', 'collaboration', 'context', 'engineering', 'hook', 'index', 'init', 'inspect', 'migrate', 'prompt', 'stack', 'verify']);
|
|
67
77
|
|
|
68
78
|
function parseCommand(argv) {
|
|
69
79
|
if (argv.length === 0 || argv.includes('--help') || argv.includes('-h') || argv[0] === 'help') {
|
|
@@ -97,7 +107,21 @@ function parseCommand(argv) {
|
|
|
97
107
|
if (options.task !== undefined && command !== 'prompt') {
|
|
98
108
|
fail('CLI_OPTION_NOT_APPLICABLE', `Option --task is not applicable to ${command}.`);
|
|
99
109
|
}
|
|
100
|
-
if (command === '
|
|
110
|
+
if (command === 'collaboration') {
|
|
111
|
+
const [action] = operands;
|
|
112
|
+
if (!['show', 'set'].includes(action)) {
|
|
113
|
+
fail('CLI_COLLABORATION_ACTION_REQUIRED', 'Command collaboration requires show or set.');
|
|
114
|
+
}
|
|
115
|
+
if (action === 'show' && operands.length !== 1) {
|
|
116
|
+
fail('CLI_EXTRA_ARGUMENT', 'Command collaboration show accepts no extra arguments.');
|
|
117
|
+
}
|
|
118
|
+
if (action === 'set' && operands.length < 5) {
|
|
119
|
+
fail(
|
|
120
|
+
'CLI_COLLABORATION_VALUES_REQUIRED',
|
|
121
|
+
'Command collaboration set requires tone, response length, experience, and explanation style.',
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
} else if (command === 'stack') {
|
|
101
125
|
if (!['list', 'add'].includes(operands[0])) {
|
|
102
126
|
fail('CLI_STACK_ACTION_REQUIRED', 'Command stack requires list or add.');
|
|
103
127
|
}
|
|
@@ -136,8 +160,8 @@ function parseCommand(argv) {
|
|
|
136
160
|
'Command inspect requires environment or section <name>.',
|
|
137
161
|
);
|
|
138
162
|
}
|
|
139
|
-
} else if (command === 'hook' && (operands.length !== 1 || !['discover', 'session'].includes(operands[0]))) {
|
|
140
|
-
fail('CLI_HOOK_ACTION_REQUIRED', 'Command hook requires exactly one of: discover, session.');
|
|
163
|
+
} else if (command === 'hook' && (operands.length !== 1 || !['discover', 'session', 'turn'].includes(operands[0]))) {
|
|
164
|
+
fail('CLI_HOOK_ACTION_REQUIRED', 'Command hook requires exactly one of: discover, session, turn.');
|
|
141
165
|
} else if (!['adopt', 'context', 'hook', 'index', 'inspect', 'prompt'].includes(command) && operands.length > 0) {
|
|
142
166
|
fail('CLI_EXTRA_ARGUMENT', `Command ${command} accepts no arguments.`);
|
|
143
167
|
}
|
|
@@ -159,6 +183,7 @@ function writeCheck(result) {
|
|
|
159
183
|
const version = format.projectVersion === null ? format.status : format.projectVersion;
|
|
160
184
|
line(process.stdout, `Project format: ${version} (${format.status}; CLI supports ${format.supportedVersion})`);
|
|
161
185
|
line(process.stdout, `Blueprint: ${result.blueprint}`);
|
|
186
|
+
line(process.stdout, `Collaboration approach: ${result.collaboration}`);
|
|
162
187
|
line(process.stdout, `Engineering approach: ${result.engineering}`);
|
|
163
188
|
line(process.stdout, `Stack: ${result.stack}`);
|
|
164
189
|
line(process.stdout, `Agent Skills: ${result.skills}`);
|
|
@@ -174,6 +199,21 @@ function writeCheck(result) {
|
|
|
174
199
|
line(process.stdout, `Check: ${result.status}`);
|
|
175
200
|
}
|
|
176
201
|
|
|
202
|
+
function writeCollaboration(result) {
|
|
203
|
+
line(process.stdout, `Tone: ${result.tone}`);
|
|
204
|
+
line(process.stdout, `Response length: ${result.responseLength}`);
|
|
205
|
+
line(process.stdout, `Assumed experience: ${result.experience}`);
|
|
206
|
+
line(process.stdout, `Explanation style: ${result.explanationStyle}`);
|
|
207
|
+
if (result.requirements) {
|
|
208
|
+
line(process.stdout, 'Project requirements:');
|
|
209
|
+
line(process.stdout, result.requirements);
|
|
210
|
+
}
|
|
211
|
+
if (result.action === 'set') {
|
|
212
|
+
namedItems('Changed files', result.changedFiles);
|
|
213
|
+
line(process.stdout, `collaboration: ${result.status}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
177
217
|
function writeEngineering(result) {
|
|
178
218
|
if (result.action === 'list') {
|
|
179
219
|
for (const profile of result.profiles) {
|
|
@@ -236,6 +276,10 @@ function writeResult(command, result) {
|
|
|
236
276
|
writeEngineering(result);
|
|
237
277
|
return;
|
|
238
278
|
}
|
|
279
|
+
if (command === 'collaboration') {
|
|
280
|
+
writeCollaboration(result);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
239
283
|
if (command === 'context') {
|
|
240
284
|
process.stdout.write(result.context.endsWith('\n') ? result.context : `${result.context}\n`);
|
|
241
285
|
return;
|
|
@@ -263,7 +307,7 @@ function writeResult(command, result) {
|
|
|
263
307
|
return;
|
|
264
308
|
}
|
|
265
309
|
if (command === 'hook') {
|
|
266
|
-
if (['discover', 'session'].includes(result.kind) && result.output) line(process.stdout, result.output);
|
|
310
|
+
if (['discover', 'session', 'turn'].includes(result.kind) && result.output) line(process.stdout, result.output);
|
|
267
311
|
return;
|
|
268
312
|
}
|
|
269
313
|
if (command === 'stack' && Array.isArray(result.pieces)) {
|
|
@@ -292,6 +336,32 @@ function writeResult(command, result) {
|
|
|
292
336
|
line(process.stdout, `${command}: ${result.status}`);
|
|
293
337
|
}
|
|
294
338
|
|
|
339
|
+
async function hookSessionId(environment = process.env) {
|
|
340
|
+
if (!environment[HOST_CONTEXT_RESOLVER_ENV]) return null;
|
|
341
|
+
let source = environment[HOST_CONTEXT_INPUT_ENV];
|
|
342
|
+
if (source === undefined && !process.stdin.isTTY) {
|
|
343
|
+
const chunks = [];
|
|
344
|
+
let bytes = 0;
|
|
345
|
+
for await (const chunk of process.stdin) {
|
|
346
|
+
bytes += Buffer.byteLength(chunk);
|
|
347
|
+
if (bytes > 16 * 1024) {
|
|
348
|
+
fail('HOST_CONTEXT_INPUT_INVALID', 'The host hook input exceeds 16 KiB.');
|
|
349
|
+
}
|
|
350
|
+
chunks.push(chunk);
|
|
351
|
+
}
|
|
352
|
+
source = Buffer.concat(chunks).toString('utf8');
|
|
353
|
+
}
|
|
354
|
+
if (!String(source || '').trim()) return null;
|
|
355
|
+
let input;
|
|
356
|
+
try {
|
|
357
|
+
input = JSON.parse(source);
|
|
358
|
+
} catch {
|
|
359
|
+
fail('HOST_CONTEXT_INPUT_INVALID', 'The host hook input must contain JSON.');
|
|
360
|
+
}
|
|
361
|
+
const sessionId = input?.sessionId ?? input?.session_id;
|
|
362
|
+
return typeof sessionId === 'string' && sessionId.trim() ? sessionId.trim() : null;
|
|
363
|
+
}
|
|
364
|
+
|
|
295
365
|
async function cliStackPackages(projectRoot, supplied) {
|
|
296
366
|
if (supplied.length > 0) return supplied;
|
|
297
367
|
try {
|
|
@@ -312,6 +382,22 @@ async function execute({ command, operands, options }, { signal } = {}) {
|
|
|
312
382
|
return adoptProject({ projectRoot, request: operands.join(' '), stackPackages });
|
|
313
383
|
}
|
|
314
384
|
if (command === 'codex') return installCodex();
|
|
385
|
+
if (command === 'collaboration') {
|
|
386
|
+
if (operands[0] === 'show') {
|
|
387
|
+
return { action: 'show', ...await inspectCollaboration({ projectRoot }) };
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
action: 'set',
|
|
391
|
+
...await setCollaboration({
|
|
392
|
+
projectRoot,
|
|
393
|
+
tone: operands[1],
|
|
394
|
+
responseLength: operands[2],
|
|
395
|
+
experience: operands[3],
|
|
396
|
+
explanationStyle: operands[4],
|
|
397
|
+
...(operands.length > 5 ? { requirements: operands.slice(5).join(' ') } : {}),
|
|
398
|
+
}),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
315
401
|
if (command === 'engineering') {
|
|
316
402
|
if (operands[0] === 'list') {
|
|
317
403
|
return { action: 'list', status: 'ok', profiles: await listEngineeringProfiles() };
|
|
@@ -379,7 +465,38 @@ async function execute({ command, operands, options }, { signal } = {}) {
|
|
|
379
465
|
if (operands[0] === 'discover') {
|
|
380
466
|
return { kind: 'discover', ...await codexAdoptionRecommendation({ projectRoot }) };
|
|
381
467
|
}
|
|
382
|
-
|
|
468
|
+
const scope = operands[0];
|
|
469
|
+
if (scope === 'session' && process.env[SESSION_CONTEXT_INSTALLED_ENV] === '1') {
|
|
470
|
+
return { kind: 'session', status: 'ready', output: '' };
|
|
471
|
+
}
|
|
472
|
+
const providerSessionId = await hookSessionId();
|
|
473
|
+
const hostContext = await resolveConfiguredHostContext({
|
|
474
|
+
projectRoot,
|
|
475
|
+
providerSessionId,
|
|
476
|
+
scope,
|
|
477
|
+
});
|
|
478
|
+
if (scope === 'turn') {
|
|
479
|
+
return hostContext === null
|
|
480
|
+
? { kind: 'turn', status: 'ready', output: '' }
|
|
481
|
+
: {
|
|
482
|
+
kind: 'turn',
|
|
483
|
+
...await projectTurnContext({
|
|
484
|
+
hostDriver: () => hostContext,
|
|
485
|
+
hostDriverInput: { scope: 'turn' },
|
|
486
|
+
}),
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
kind: 'session',
|
|
491
|
+
...await projectSessionContext({
|
|
492
|
+
...(hostContext === null ? {} : {
|
|
493
|
+
hostDriver: () => hostContext,
|
|
494
|
+
hostDriverInput: { scope: 'session' },
|
|
495
|
+
}),
|
|
496
|
+
projectRoot,
|
|
497
|
+
stackPackages,
|
|
498
|
+
}),
|
|
499
|
+
};
|
|
383
500
|
}
|
|
384
501
|
if (command === 'verify') {
|
|
385
502
|
return verify({
|
package/src/index/check.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readBlueprint } from './blueprint.js';
|
|
2
|
+
import { readCollaboration } from './collaboration.js';
|
|
2
3
|
import { inspectProjectSkills } from './agent-skills.js';
|
|
3
4
|
import { readStack } from './stack.js';
|
|
4
5
|
import { asDiagnostic } from './errors.js';
|
|
@@ -19,6 +20,7 @@ function invalidResult(area, error, projectFormat) {
|
|
|
19
20
|
status: 'invalid',
|
|
20
21
|
projectFormat,
|
|
21
22
|
blueprint: area === 'blueprint' ? 'invalid' : 'valid',
|
|
23
|
+
collaboration: area === 'collaboration' ? 'invalid' : 'unknown',
|
|
22
24
|
engineering: area === 'engineering' ? 'invalid' : 'unknown',
|
|
23
25
|
stack: area === 'stack' ? 'invalid' : 'unknown',
|
|
24
26
|
skills: area === 'skills' ? 'invalid' : 'unknown',
|
|
@@ -43,6 +45,7 @@ function formatMismatchResult(projectFormat) {
|
|
|
43
45
|
status,
|
|
44
46
|
projectFormat,
|
|
45
47
|
blueprint: 'unknown',
|
|
48
|
+
collaboration: 'unknown',
|
|
46
49
|
engineering: 'unknown',
|
|
47
50
|
stack: 'unknown',
|
|
48
51
|
skills: 'unknown',
|
|
@@ -75,7 +78,13 @@ export async function checkProject({
|
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
let stack;
|
|
81
|
+
let collaboration;
|
|
78
82
|
let engineering;
|
|
83
|
+
try {
|
|
84
|
+
collaboration = await readCollaboration(root);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return invalidResult('collaboration', error, projectFormat);
|
|
87
|
+
}
|
|
79
88
|
try {
|
|
80
89
|
engineering = await readEngineering(root);
|
|
81
90
|
} catch (error) {
|
|
@@ -154,6 +163,7 @@ export async function checkProject({
|
|
|
154
163
|
: needsAttention ? 'attention' : 'ok',
|
|
155
164
|
projectFormat,
|
|
156
165
|
blueprint: 'valid',
|
|
166
|
+
collaboration: collaboration.status,
|
|
157
167
|
engineering: engineering.status,
|
|
158
168
|
stack: 'valid',
|
|
159
169
|
skills: skills.status,
|