genesis-compiler 1.8.5 → 1.9.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 +11 -2
- package/docs/prompt-integration.md +4 -0
- package/docs/stack-components.md +48 -3
- package/docs/templates.md +3 -0
- package/package.json +1 -1
- package/src/cli.js +21 -20
- package/src/index/check.js +1 -7
- package/src/index/code-index.js +3 -2
- package/src/index/init.js +10 -4
- package/src/index/migration.js +8 -1
- package/src/index/project-inspection.js +4 -0
- package/src/index/stack-project-contracts.js +72 -11
- package/src/index/stack-section-inspection.js +33 -17
- package/src/index/stack-section.js +50 -8
- package/src/index/stack.js +33 -3
- package/src/index.js +15 -4
package/README.md
CHANGED
|
@@ -860,8 +860,10 @@ genesis check
|
|
|
860
860
|
- Blueprint, engineering approach, and Stack validity;
|
|
861
861
|
- selected Agent Skill presence and structural validity;
|
|
862
862
|
- Program presence and structural validity;
|
|
863
|
-
- environment declarations
|
|
864
|
-
`
|
|
863
|
+
- environment declarations as `ready`, `missing-inputs`, or honestly
|
|
864
|
+
`unconfigured`, and consumer-owned Stack extensions as `ready` or
|
|
865
|
+
`unconfigured` (an undecided competing section makes the Stack invalid with
|
|
866
|
+
`STACK_SECTION_AMBIGUOUS` until the project authors it);
|
|
865
867
|
- missing Stack-declared environment inputs (without claiming the service is
|
|
866
868
|
reachable);
|
|
867
869
|
- verification evidence as `current`, `stale`, `missing`, `invalid`, or
|
|
@@ -1005,6 +1007,13 @@ repository. It retains the same Stack validation and performs no writes or
|
|
|
1005
1007
|
commands. Missing/invalid roots fail; this is never an automatic fallback from
|
|
1006
1008
|
Git ownership or discovery errors.
|
|
1007
1009
|
|
|
1010
|
+
`inspectStackSections({ names, projectRoot, rootMode, stackPackages })` returns
|
|
1011
|
+
an array of those same section results in the requested order. Names must be
|
|
1012
|
+
distinct and the array must be nonempty. It discovers the root once and reads
|
|
1013
|
+
the Stack once, so related sections share one Stack identity. Each call reads
|
|
1014
|
+
current files; results are not cached across calls. Missing sections retain
|
|
1015
|
+
their ordinary `unconfigured` result and repository/directory rules are unchanged.
|
|
1016
|
+
|
|
1008
1017
|
Hosts that already validate access to a shared worktree may wrap an operation
|
|
1009
1018
|
in `withTrustedGitRepository(absoluteWorktreeRoot, () => inspectEnvironment({
|
|
1010
1019
|
projectRoot: absoluteWorktreeRoot }))`. This explicitly trusts that one
|
|
@@ -99,6 +99,10 @@ The executable returns exactly one JSON result:
|
|
|
99
99
|
means the host already delivers through a native system field. This result
|
|
100
100
|
is valid only for session scope.
|
|
101
101
|
|
|
102
|
+
A validated complete host delivery is resolved before CLI project-format, Git
|
|
103
|
+
root, or Stack inspection. Unclaimed conversations and host contributions retain
|
|
104
|
+
the normal project diagnostics and composition. The resolver runs once per hook.
|
|
105
|
+
|
|
102
106
|
Malformed results and configured-resolver failures fail the hook. An unavailable
|
|
103
107
|
claimed owner must fail rather than returning `unclaimed`. Ownership belongs to
|
|
104
108
|
the native conversation: these results do not change project hook files, so
|
package/docs/stack-components.md
CHANGED
|
@@ -157,9 +157,20 @@ validate a private schema, map runtimes, execute commands, or prove success.
|
|
|
157
157
|
Proposal composition is intentionally small:
|
|
158
158
|
|
|
159
159
|
1. An existing project section remains authoritative and is never overwritten.
|
|
160
|
-
2.
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
2. A component's proposal supersedes the proposals of selected components it
|
|
161
|
+
requires, directly or transitively. Laravel's proposal therefore replaces
|
|
162
|
+
the PHP and Node proposals for the same heading.
|
|
163
|
+
3. Without a project section, exactly one remaining proposal is copied
|
|
164
|
+
unchanged.
|
|
165
|
+
4. Several remaining proposals from unrelated components are a project
|
|
166
|
+
decision. `genesis stack add` still succeeds, materializes every other
|
|
167
|
+
section, leaves that heading unwritten, and returns a
|
|
168
|
+
`STACK_SECTION_AMBIGUOUS` diagnostic whose details list each competing
|
|
169
|
+
source and its exact lines. Its preparation prompt quotes the proposals and
|
|
170
|
+
asks the agent to author one complete project section first. Current-format
|
|
171
|
+
reads, `genesis check`, inspection and context reject the Stack with the
|
|
172
|
+
same message until that section exists. Genesis never merges the bodies or
|
|
173
|
+
guesses a winner.
|
|
163
174
|
|
|
164
175
|
After selection every effective opaque section is project-owned. The component
|
|
165
176
|
catalog may improve later without silently changing the application's setup,
|
|
@@ -184,6 +195,13 @@ discovery. Default library and CLI inspection continue to use the enclosing
|
|
|
184
195
|
Git repository and its trust checks. Invalid modes or directory roots fail;
|
|
185
196
|
there is no automatic fallback.
|
|
186
197
|
|
|
198
|
+
When a consumer needs several related declarations, use
|
|
199
|
+
`inspectStackSections({ names: ['First operation', 'Second operation'], projectRoot })`.
|
|
200
|
+
It returns an array of the same `genesis.stack-section.v1` results in the requested
|
|
201
|
+
order, using one repository discovery and one fresh Stack read. All results have
|
|
202
|
+
the same Stack hash. The names array must be nonempty and contain no duplicates.
|
|
203
|
+
The explicit directory mode is also supported; no cross-call cache is retained.
|
|
204
|
+
|
|
187
205
|
## Selection and identity
|
|
188
206
|
|
|
189
207
|
`genesis stack add` records selected packages and component dependency closure,
|
|
@@ -263,5 +281,32 @@ Project format 2 removes runtime inheritance of operation declarations from
|
|
|
263
281
|
the catalog. `genesis migrate` upgrades format 1 projects by composing the
|
|
264
282
|
previously effective declarations once and writing them into
|
|
265
283
|
`genesis/stack.md`. Existing project sections win exactly as they did before.
|
|
284
|
+
Competing proposals that no requirement resolves do not fail the migration:
|
|
285
|
+
it completes, leaves that heading unwritten, and returns the same
|
|
286
|
+
`STACK_SECTION_AMBIGUOUS` diagnostic and decision prompt as `genesis stack add`.
|
|
266
287
|
Current-format reads reject a selected component whose required contract was
|
|
267
288
|
not materialized, so a tool upgrade cannot quietly substitute a new proposal.
|
|
289
|
+
|
|
290
|
+
`genesis migrate` does not materialize newly added catalog proposals when the
|
|
291
|
+
project already uses the current format. Reconcile those explicitly with the
|
|
292
|
+
existing `genesis stack add` operation for an already-selected component. For
|
|
293
|
+
example, if `nodejs` is selected and the missing heading is
|
|
294
|
+
`## Connected development`, run `genesis stack add nodejs` and review its
|
|
295
|
+
preparation prompt. This adds the Node `npm` default without choosing connected
|
|
296
|
+
repositories or installing dependencies. Every package-ecosystem language piece
|
|
297
|
+
proposes its own default the same way (PHP `composer`, Python `pip`, Ruby
|
|
298
|
+
`bundler`, Go `gomod`, Rust `cargo`, Java `maven`, Kotlin `gradle`, C#
|
|
299
|
+
`nuget`); Laravel proposes `composer`, superseding the PHP and Node proposals it
|
|
300
|
+
requires. C/C++ and shell propose none. When several unrelated languages are
|
|
301
|
+
selected, the add reports the competing defaults and the project authors one
|
|
302
|
+
`## Connected development` section choosing its default. Existing complete
|
|
303
|
+
project sections remain authoritative; reconcile any proposed operation against
|
|
304
|
+
the real source. Use the project's pinned compiler invocation when it declares
|
|
305
|
+
`genesis-compiler`: `npm exec --no -- genesis stack add nodejs`.
|
|
306
|
+
|
|
307
|
+
Before rolling out a catalog that adds operation proposals, operators should
|
|
308
|
+
run read-only project inspection against representative existing projects with
|
|
309
|
+
the candidate compiler and catalog. Arrange explicit reconciliation of affected
|
|
310
|
+
projects before rollout. A format marker alone does not establish compatibility
|
|
311
|
+
with new catalog proposals. Do not repair project contracts through opening,
|
|
312
|
+
status reads or another lazy write path.
|
package/docs/templates.md
CHANGED
|
@@ -14,6 +14,9 @@ application has been described or that an application has been created.
|
|
|
14
14
|
`genesis inspect project --json` is a read-only opening inspection shared by
|
|
15
15
|
the CLI, session guidance, and hosts such as Vibe64:
|
|
16
16
|
|
|
17
|
+
The diagnostics list reports each identical failure once, even when separate
|
|
18
|
+
inspectors encounter it. Distinct failures remain available for repair.
|
|
19
|
+
|
|
17
20
|
| State | Evidence | Next step |
|
|
18
21
|
| --- | --- | --- |
|
|
19
22
|
| `new` | No content beyond known bootstrap files | Choose a template or create through conversation |
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -471,7 +471,7 @@ async function cliStackPackages(projectRoot, supplied) {
|
|
|
471
471
|
return installedFirstPartyStackPackages({ projectRoot });
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
-
async function execute({ command, operands, options }, { signal } = {}) {
|
|
474
|
+
async function execute({ command, operands, options }, { signal, hostContext = null } = {}) {
|
|
475
475
|
if (command === 'parsers') {
|
|
476
476
|
const parserOptions = {
|
|
477
477
|
languages: operands.slice(1), all: options.all, directory: options.directory, signal,
|
|
@@ -576,23 +576,6 @@ async function execute({ command, operands, options }, { signal } = {}) {
|
|
|
576
576
|
return { kind: 'discover', ...await codexAdoptionRecommendation({ projectRoot }) };
|
|
577
577
|
}
|
|
578
578
|
const scope = operands[0];
|
|
579
|
-
if (scope === 'turn' && process.env.GENESIS_TURN_CONTEXT_ENABLED === '0') {
|
|
580
|
-
return { kind: 'turn', status: 'ready', output: '' };
|
|
581
|
-
}
|
|
582
|
-
if (scope === 'session' && process.env[SESSION_CONTEXT_INSTALLED_ENV] === '1') {
|
|
583
|
-
return { kind: 'session', status: 'ready', output: '' };
|
|
584
|
-
}
|
|
585
|
-
const nativeSession = await hookSessionInput();
|
|
586
|
-
const hostContext = await resolveConfiguredHostContext({
|
|
587
|
-
projectRoot,
|
|
588
|
-
...nativeSession,
|
|
589
|
-
scope,
|
|
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
|
-
}
|
|
596
579
|
if (scope === 'turn') {
|
|
597
580
|
return hostContext === null
|
|
598
581
|
? { kind: 'turn', status: 'ready', output: '' }
|
|
@@ -669,7 +652,25 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
669
652
|
return 0;
|
|
670
653
|
}
|
|
671
654
|
options = parsed.options;
|
|
672
|
-
const
|
|
655
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
656
|
+
let hookResult = null;
|
|
657
|
+
let hostContext = null;
|
|
658
|
+
if (parsed.command === 'hook' && parsed.operands[0] !== 'discover') {
|
|
659
|
+
const scope = parsed.operands[0];
|
|
660
|
+
if ((scope === 'turn' && process.env.GENESIS_TURN_CONTEXT_ENABLED === '0') ||
|
|
661
|
+
(scope === 'session' && process.env[SESSION_CONTEXT_INSTALLED_ENV] === '1')) {
|
|
662
|
+
hookResult = { kind: scope, status: 'ready', output: '' };
|
|
663
|
+
} else {
|
|
664
|
+
const nativeSession = await hookSessionInput();
|
|
665
|
+
hostContext = await resolveConfiguredHostContext({ projectRoot, ...nativeSession, scope });
|
|
666
|
+
// A validated owner supplies the complete delivery; project composition is unused.
|
|
667
|
+
if (hostContext?.kind === 'delivery') {
|
|
668
|
+
hookResult = { kind: 'session', status: 'ready', output: hostContext.text };
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
const projectFormat = hookResult || parsed.command === 'parsers'
|
|
673
|
+
? null : await optionalProjectFormat(projectRoot);
|
|
673
674
|
const formatDiagnostic = projectFormatDiagnostic(projectFormat);
|
|
674
675
|
if (formatDiagnostic && !options.json) {
|
|
675
676
|
line(process.stderr, `WARNING: ${formatDiagnostic.message}`);
|
|
@@ -680,7 +681,7 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
680
681
|
process.once('SIGINT', abort);
|
|
681
682
|
process.once('SIGTERM', abort);
|
|
682
683
|
const result = withProjectFormatWarning(
|
|
683
|
-
await withParserCancellation(controller.signal, () => execute(parsed, { signal: controller.signal })),
|
|
684
|
+
hookResult ?? await withParserCancellation(controller.signal, () => execute(parsed, { signal: controller.signal, hostContext })),
|
|
684
685
|
projectFormat,
|
|
685
686
|
formatDiagnostic,
|
|
686
687
|
);
|
package/src/index/check.js
CHANGED
|
@@ -124,12 +124,10 @@ export async function checkProject({
|
|
|
124
124
|
projectRoot: root,
|
|
125
125
|
stackPackages,
|
|
126
126
|
});
|
|
127
|
-
const extensionDiagnostics = stack.extensions.flatMap(({ diagnostics }) => diagnostics);
|
|
128
127
|
const diagnostics = [
|
|
129
128
|
...(program.diagnostic ? [program.diagnostic] : []),
|
|
130
129
|
...skills.diagnostics,
|
|
131
130
|
...missingResources,
|
|
132
|
-
...extensionDiagnostics,
|
|
133
131
|
...(verification.status === 'invalid' ? [{
|
|
134
132
|
code: 'VERIFICATION_EVIDENCE_INVALID',
|
|
135
133
|
message: 'Saved verification evidence is malformed.',
|
|
@@ -145,7 +143,6 @@ export async function checkProject({
|
|
|
145
143
|
if (missingResources.length > 0) {
|
|
146
144
|
guidance.push(`${missingResources.map(({ message }) => message).join(' ')} Prompt generation remains available.`);
|
|
147
145
|
}
|
|
148
|
-
if (extensionDiagnostics.length > 0) guidance.push('Resolve the competing consumer-owned Stack sections.');
|
|
149
146
|
if (['missing', 'stale'].includes(verification.status)) guidance.push('Run genesis verify to refresh concrete evidence.');
|
|
150
147
|
if (verification.status === 'unconfigured') guidance.push('Add project verification commands to genesis/stack.md.');
|
|
151
148
|
guidance.push('Use genesis prompt --task review for a semantic, evidence-based comparison.');
|
|
@@ -154,7 +151,6 @@ export async function checkProject({
|
|
|
154
151
|
|| engineering.status === 'defaulted'
|
|
155
152
|
|| ['missing', 'outdated'].includes(skills.status)
|
|
156
153
|
|| missingResources.length > 0
|
|
157
|
-
|| extensionDiagnostics.length > 0
|
|
158
154
|
|| ['missing', 'stale', 'unconfigured'].includes(verification.status);
|
|
159
155
|
|
|
160
156
|
return {
|
|
@@ -169,9 +165,7 @@ export async function checkProject({
|
|
|
169
165
|
skills: skills.status,
|
|
170
166
|
program: program.status,
|
|
171
167
|
environment: projectEnvironment.status,
|
|
172
|
-
extensions:
|
|
173
|
-
? 'blocked'
|
|
174
|
-
: stack.extensions.length > 0 ? 'ready' : 'unconfigured',
|
|
168
|
+
extensions: stack.extensions.length > 0 ? 'ready' : 'unconfigured',
|
|
175
169
|
resources: missingResources.length > 0 ? 'missing' : 'inputs-present',
|
|
176
170
|
verification: verification.status,
|
|
177
171
|
programFiles: program.files,
|
package/src/index/code-index.js
CHANGED
|
@@ -253,6 +253,7 @@ function formatCount(value, singular) {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
export async function buildProjectIndex({
|
|
256
|
+
allowUnresolvedSections = false,
|
|
256
257
|
projectRoot,
|
|
257
258
|
queries = [],
|
|
258
259
|
stackPackages = [],
|
|
@@ -264,7 +265,7 @@ export async function buildProjectIndex({
|
|
|
264
265
|
const observationReader = typeof observations === 'function' ? observations : null;
|
|
265
266
|
if (observationReader) observations = null;
|
|
266
267
|
const root = (await gitContext(projectRoot)).repositoryRoot;
|
|
267
|
-
const stack = await readStack(root, { stackPackages });
|
|
268
|
+
const stack = await readStack(root, { allowUnresolvedSections, stackPackages });
|
|
268
269
|
const packageRoots = stack.packageDiscovery.discoveries.flatMap(discovery => discovery.manifests.map(manifest => path.posix.join(path.posix.dirname(manifest), discovery.packageRoot)));
|
|
269
270
|
const includePath = filePath => isProjectContentPath(filePath) && !packageRoots.some(directory => filePath === directory || filePath.startsWith(`${directory}/`));
|
|
270
271
|
const states = await gitVisibleFileStates(root, { includePath });
|
|
@@ -322,7 +323,7 @@ export async function buildProjectIndex({
|
|
|
322
323
|
// valid artifact after an inspector/parser read racing a source change.
|
|
323
324
|
const finalStates = await gitVisibleFileStates(root, { includePath });
|
|
324
325
|
const finalFiles = [...finalStates].filter(([filePath, state]) => state.exists && !state.symlink && !state.special && state.hash && !cityPathExcluded(filePath, stack.cityExclusions)).map(([filePath, state]) => ({ path: filePath, hash: state.hash, mode: state.mode }));
|
|
325
|
-
const finalStack = await readStack(root, { stackPackages });
|
|
326
|
+
const finalStack = await readStack(root, { allowUnresolvedSections, stackPackages });
|
|
326
327
|
const finalMap = await inspectSubsystems({ projectRoot: root }).catch(() => null);
|
|
327
328
|
if (stableJson(finalFiles) !== stableJson(files) || finalStack.identityHash !== stack.identityHash || (finalMap?.identity || null) !== (subsystemMap?.identity || null)) {
|
|
328
329
|
throw new GenesisError('ARCHITECTURE_SOURCE_CHANGED', 'Source or Stack changed during architecture analysis. The previous index is retained; refresh after the changes settle.');
|
package/src/index/init.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
inspectProjectFormatAtRoot,
|
|
22
22
|
requireCurrentProjectFormat,
|
|
23
23
|
} from './project-format.js';
|
|
24
|
-
import { EMPTY_STACK_SOURCE, readStack } from './stack.js';
|
|
24
|
+
import { EMPTY_STACK_SOURCE, readStack, stackDecisionResult } from './stack.js';
|
|
25
25
|
|
|
26
26
|
async function createIfMissing(projectRoot, relativePath, source) {
|
|
27
27
|
const location = path.join(projectRoot, relativePath);
|
|
@@ -35,7 +35,11 @@ async function createIfMissing(projectRoot, relativePath, source) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export async function initializeProject({
|
|
38
|
+
export async function initializeProject({
|
|
39
|
+
allowUnresolvedSections = false,
|
|
40
|
+
projectRoot,
|
|
41
|
+
stackPackages = [],
|
|
42
|
+
} = {}) {
|
|
39
43
|
const root = (await gitContext(projectRoot)).repositoryRoot;
|
|
40
44
|
const projectFormat = await inspectProjectFormatAtRoot(root);
|
|
41
45
|
requireCurrentProjectFormat(projectFormat, { allowUninitialized: true });
|
|
@@ -47,7 +51,8 @@ export async function initializeProject({ projectRoot, stackPackages = [] } = {}
|
|
|
47
51
|
createIfMissing(root, STACK_PATH, EMPTY_STACK_SOURCE),
|
|
48
52
|
])).filter(Boolean);
|
|
49
53
|
await mkdir(path.join(root, PROGRAM_ROOT), { recursive: true });
|
|
50
|
-
const stack = await readStack(root, { stackPackages });
|
|
54
|
+
const stack = await readStack(root, { allowUnresolvedSections, stackPackages });
|
|
55
|
+
const decisions = stackDecisionResult(stack);
|
|
51
56
|
const skills = await syncProjectSkills({ projectRoot: root, stack });
|
|
52
57
|
const integrations = await syncAgentIntegrations({ projectRoot: root });
|
|
53
58
|
const version = projectFormat.status === 'uninitialized'
|
|
@@ -67,7 +72,8 @@ export async function initializeProject({ projectRoot, stackPackages = [] } = {}
|
|
|
67
72
|
? 'Initialized Genesis, Agent Skills, and project guidance for Codex, Claude Code and OpenCode.'
|
|
68
73
|
: 'Genesis, its Agent Skills, and project guidance for Codex, Claude Code and OpenCode are already initialized.',
|
|
69
74
|
changedFiles,
|
|
70
|
-
diagnostics: skills.diagnostics,
|
|
75
|
+
diagnostics: [...decisions.diagnostics, ...skills.diagnostics],
|
|
76
|
+
...(decisions.prompt ? { prompt: decisions.prompt } : {}),
|
|
71
77
|
...(integrations.warnings ? { warnings: integrations.warnings } : {}),
|
|
72
78
|
guidance: [
|
|
73
79
|
'Open Codex and use /hooks to review and trust the project guidance hook.',
|
package/src/index/migration.js
CHANGED
|
@@ -79,7 +79,13 @@ export async function migrateProject({ projectRoot, stackPackages = [] } = {}) {
|
|
|
79
79
|
migratedFiles.push(PROJECT_VERSION_PATH);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
// Like selection, migration completes and reports competing Stack sections
|
|
83
|
+
// as project decisions; ordinary reads keep rejecting until they are authored.
|
|
84
|
+
const initialized = await initializeProject({
|
|
85
|
+
allowUnresolvedSections: true,
|
|
86
|
+
projectRoot: root,
|
|
87
|
+
stackPackages,
|
|
88
|
+
});
|
|
83
89
|
const changedFiles = uniqueSorted([...migratedFiles, ...initialized.changedFiles]);
|
|
84
90
|
const migrated = initial.status !== 'current';
|
|
85
91
|
return {
|
|
@@ -92,5 +98,6 @@ export async function migrateProject({ projectRoot, stackPackages = [] } = {}) {
|
|
|
92
98
|
projectFormat: await inspectProjectFormatAtRoot(root),
|
|
93
99
|
changedFiles,
|
|
94
100
|
diagnostics: initialized.diagnostics,
|
|
101
|
+
...(initialized.prompt ? { prompt: initialized.prompt } : {}),
|
|
95
102
|
};
|
|
96
103
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
3
4
|
|
|
4
5
|
import { readBlueprint } from './blueprint.js';
|
|
5
6
|
import { readCollaboration } from './collaboration.js';
|
|
@@ -125,6 +126,9 @@ export async function inspectProject({ projectRoot, stackPackages = [] } = {}) {
|
|
|
125
126
|
try { await read(root); } catch (error) { result.diagnostics.push(asDiagnostic(error)); }
|
|
126
127
|
}
|
|
127
128
|
try { await inspectProgram(root); } catch (error) { result.diagnostics.push(asDiagnostic(error)); }
|
|
129
|
+
// Program resource validation can encounter the same Stack failure already reported above.
|
|
130
|
+
result.diagnostics = result.diagnostics.filter((diagnostic, index, diagnostics) =>
|
|
131
|
+
!diagnostics.slice(0, index).some((previous) => isDeepStrictEqual(previous, diagnostic)));
|
|
128
132
|
if (result.diagnostics.length) return result;
|
|
129
133
|
if (content.kind !== 'existing') {
|
|
130
134
|
return { ...result, state: 'new', templateEligible: true, nextAction: 'create' };
|
|
@@ -112,10 +112,12 @@ function contractSections({ components, knownSectionNames, sections }) {
|
|
|
112
112
|
components,
|
|
113
113
|
opaqueStackSections(sections, knownSectionNames),
|
|
114
114
|
);
|
|
115
|
+
const unresolvedSections = [];
|
|
115
116
|
for (const extension of extensions) {
|
|
116
117
|
const [diagnostic] = extension.diagnostics;
|
|
117
118
|
if (diagnostic) {
|
|
118
|
-
|
|
119
|
+
unresolvedSections.push({ name: extension.name, diagnostic });
|
|
120
|
+
continue;
|
|
119
121
|
}
|
|
120
122
|
projectLines.set(extension.name, trimStackSectionLines(extension.lines));
|
|
121
123
|
if (!sections.has(extension.name)) materializedSections.push(extension.name);
|
|
@@ -130,15 +132,36 @@ function contractSections({ components, knownSectionNames, sections }) {
|
|
|
130
132
|
materializedSections,
|
|
131
133
|
projectContracts: [...projectLines].map(([name, lines]) => ({ name, lines })),
|
|
132
134
|
resourceLines: projectLines.get('Resources') ?? null,
|
|
135
|
+
unresolvedSections,
|
|
133
136
|
verificationLines: projectLines.get('Verification') ?? null,
|
|
134
137
|
};
|
|
135
138
|
}
|
|
136
139
|
|
|
140
|
+
function unresolvedSectionsError(unresolvedSections) {
|
|
141
|
+
if (unresolvedSections.length === 1) {
|
|
142
|
+
const [{ diagnostic }] = unresolvedSections;
|
|
143
|
+
return new GenesisError(diagnostic.code, diagnostic.message, diagnostic.details);
|
|
144
|
+
}
|
|
145
|
+
return new GenesisError(
|
|
146
|
+
'STACK_SECTION_AMBIGUOUS',
|
|
147
|
+
unresolvedSections.map(({ diagnostic }) => diagnostic.message).join('\n\n'),
|
|
148
|
+
{ sections: unresolvedSections.map(({ diagnostic }) => diagnostic.details) },
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
137
152
|
export function materializeStackProjectContracts(options) {
|
|
138
153
|
return contractSections(options);
|
|
139
154
|
}
|
|
140
155
|
|
|
141
|
-
|
|
156
|
+
/**
|
|
157
|
+
* Require every selected proposal to be project-owned. A competing proposal
|
|
158
|
+
* is a project decision; only the add operation that reports it may continue
|
|
159
|
+
* without it.
|
|
160
|
+
*/
|
|
161
|
+
export function requireMaterializedStackProjectContracts({
|
|
162
|
+
allowUnresolvedSections = false,
|
|
163
|
+
...options
|
|
164
|
+
}) {
|
|
142
165
|
const result = contractSections(options);
|
|
143
166
|
if (result.materializedSections.length > 0) {
|
|
144
167
|
const headings = result.materializedSections.map((name) => `\`## ${name}\``);
|
|
@@ -148,11 +171,15 @@ export function requireMaterializedStackProjectContracts(options) {
|
|
|
148
171
|
{ sections: result.materializedSections },
|
|
149
172
|
);
|
|
150
173
|
}
|
|
174
|
+
if (!allowUnresolvedSections && result.unresolvedSections.length > 0) {
|
|
175
|
+
throw unresolvedSectionsError(result.unresolvedSections);
|
|
176
|
+
}
|
|
151
177
|
return result;
|
|
152
178
|
}
|
|
153
179
|
|
|
154
|
-
function contractBlocks(projectContracts) {
|
|
180
|
+
function contractBlocks(projectContracts, hasDecisions) {
|
|
155
181
|
if (projectContracts.length === 0) {
|
|
182
|
+
if (hasDecisions) return ['No other operation contract was proposed by these components.'];
|
|
156
183
|
return [
|
|
157
184
|
'No operation contract was proposed by these components. Inspect the application you are creating and declare only its real setup, verification, output, and other consumer operations before calling it ready.',
|
|
158
185
|
];
|
|
@@ -167,14 +194,43 @@ function contractBlocks(projectContracts) {
|
|
|
167
194
|
]);
|
|
168
195
|
}
|
|
169
196
|
|
|
197
|
+
function decisionBlocks(unresolvedSections) {
|
|
198
|
+
return unresolvedSections.flatMap(({ name, diagnostic }) => [
|
|
199
|
+
`### \`## ${name}\``,
|
|
200
|
+
'',
|
|
201
|
+
...diagnostic.details.proposals.flatMap(({ source, lines }) => [
|
|
202
|
+
`Proposal from \`${source}\`:`,
|
|
203
|
+
'',
|
|
204
|
+
'~~~markdown',
|
|
205
|
+
...lines,
|
|
206
|
+
'~~~',
|
|
207
|
+
'',
|
|
208
|
+
]),
|
|
209
|
+
]);
|
|
210
|
+
}
|
|
211
|
+
|
|
170
212
|
export function renderStackPreparationPrompt({
|
|
171
213
|
components,
|
|
172
214
|
materializedSections = [],
|
|
173
215
|
newlySelected = [],
|
|
174
216
|
projectContracts = [],
|
|
175
217
|
reviewSections = [],
|
|
218
|
+
unresolvedSections = [],
|
|
176
219
|
} = {}) {
|
|
177
|
-
if (newlySelected.length === 0 && materializedSections.length === 0
|
|
220
|
+
if (newlySelected.length === 0 && materializedSections.length === 0
|
|
221
|
+
&& unresolvedSections.length === 0) return '';
|
|
222
|
+
const decisionHeadings = unresolvedSections.map(({ name }) => `\`## ${name}\``).join(', ');
|
|
223
|
+
const steps = [
|
|
224
|
+
...(unresolvedSections.length > 0 ? [
|
|
225
|
+
`Decide ${decisionHeadings} first. Selected components propose competing bodies and none requires the other, so Genesis left ${unresolvedSections.length === 1 ? 'that heading' : 'those headings'} unwritten. Author one complete project-owned section per heading in \`genesis/stack.md\`, choosing between the proposals quoted under “Project decisions required” for this application. Until then current-format Genesis reads, including \`genesis check\` and \`genesis context\`, reject the Stack with \`STACK_SECTION_AMBIGUOUS\`.`,
|
|
226
|
+
] : []),
|
|
227
|
+
'Read `genesis/blueprint.md`, `genesis/engineering.md`, and the complete `genesis/stack.md`, then run `genesis context .` (or the relevant source paths).',
|
|
228
|
+
'Load every applicable installed Agent Skill and follow the selected technology’s authoritative guidance before creating dependencies or source.',
|
|
229
|
+
'For a new project, implement the source and commands so every project contract below is true. For existing source, inspect reality first and change a complete project section when the inherited proposal is not already true; do not add compatibility code merely to imitate a proposal.',
|
|
230
|
+
'Keep each consumer-owned section opaque to Genesis. Validate it with the consumer or framework that owns its grammar and behavior.',
|
|
231
|
+
'Establish or update the non-technical Blueprint and affected Program explanations as the product becomes concrete.',
|
|
232
|
+
'Run `genesis check`, resolve structural or incomplete-contract diagnostics, and run `genesis verify` only after the declared verification commands exist. Passing verification is evidence for those commands, not proof of every consumer operation.',
|
|
233
|
+
];
|
|
178
234
|
const selected = components.map(({ id }) => `\`${id}\``).join(', ');
|
|
179
235
|
return [
|
|
180
236
|
'# Prepare the selected Stack',
|
|
@@ -188,18 +244,23 @@ export function renderStackPreparationPrompt({
|
|
|
188
244
|
'',
|
|
189
245
|
`These existing project contracts remained authoritative while the new components were added: ${reviewSections.map((name) => `\`## ${name}\``).join(', ')}. Reconcile them against the newly selected technology using source evidence; Genesis deliberately did not overwrite them.`,
|
|
190
246
|
] : []),
|
|
247
|
+
...(unresolvedSections.length > 0 ? [
|
|
248
|
+
'',
|
|
249
|
+
`Unresolved project decisions: ${decisionHeadings}.`,
|
|
250
|
+
] : []),
|
|
191
251
|
'',
|
|
192
252
|
'Continue this same task now:',
|
|
193
253
|
'',
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
254
|
+
...steps.map((step, index) => `${index + 1}. ${step}`),
|
|
255
|
+
...(unresolvedSections.length > 0 ? [
|
|
256
|
+
'',
|
|
257
|
+
'## Project decisions required',
|
|
258
|
+
'',
|
|
259
|
+
...trimStackSectionLines(decisionBlocks(unresolvedSections)),
|
|
260
|
+
] : []),
|
|
200
261
|
'',
|
|
201
262
|
'## Project contracts to satisfy',
|
|
202
263
|
'',
|
|
203
|
-
...contractBlocks(projectContracts),
|
|
264
|
+
...contractBlocks(projectContracts, unresolvedSections.length > 0),
|
|
204
265
|
].join('\n').trimEnd();
|
|
205
266
|
}
|
|
@@ -10,11 +10,25 @@ import { sha256, stableJson } from './utils.js';
|
|
|
10
10
|
/** Inspect one consumer-owned Stack section without interpreting its contents. */
|
|
11
11
|
export async function inspectProjectStackSection({
|
|
12
12
|
name,
|
|
13
|
+
...options
|
|
14
|
+
} = {}) {
|
|
15
|
+
return (await inspectProjectStackSections({ ...options, names: [name] }))[0];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Inspect several sections from one fresh Stack read, in the requested order. */
|
|
19
|
+
export async function inspectProjectStackSections({
|
|
20
|
+
names,
|
|
13
21
|
projectRoot,
|
|
14
22
|
rootMode = 'repository',
|
|
15
23
|
stackPackages = [],
|
|
16
24
|
} = {}) {
|
|
17
|
-
|
|
25
|
+
if (!Array.isArray(names) || names.length === 0) {
|
|
26
|
+
throw new TypeError('Stack section inspection requires a nonempty names array.');
|
|
27
|
+
}
|
|
28
|
+
const sectionNames = names.map(normalizeStackSectionName);
|
|
29
|
+
if (new Set(sectionNames).size !== sectionNames.length) {
|
|
30
|
+
throw new TypeError('Stack section inspection requires distinct names.');
|
|
31
|
+
}
|
|
18
32
|
let root;
|
|
19
33
|
if (rootMode === 'repository') {
|
|
20
34
|
root = (await gitContext(projectRoot)).repositoryRoot;
|
|
@@ -28,20 +42,22 @@ export async function inspectProjectStackSection({
|
|
|
28
42
|
throw new TypeError('Stack section rootMode must be repository or directory.');
|
|
29
43
|
}
|
|
30
44
|
const stack = await readStack(root, { stackPackages });
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
return sectionNames.map((sectionName) => {
|
|
46
|
+
const section = stack.extensions.find((candidate) => candidate.name === sectionName);
|
|
47
|
+
const diagnostics = [...(section?.diagnostics || [])];
|
|
48
|
+
const status = diagnostics.length > 0 ? 'blocked' : section ? 'ready' : 'unconfigured';
|
|
49
|
+
const lines = section ? [...section.lines] : [];
|
|
50
|
+
return {
|
|
51
|
+
contract: GENESIS_CONTRACTS.stackSection,
|
|
52
|
+
status,
|
|
53
|
+
stackHash: stack.identityHash,
|
|
54
|
+
sectionHash: status === 'ready' ? sha256(stableJson({ name: sectionName, lines })) : '',
|
|
55
|
+
components: stack.components.map(({ id }) => id),
|
|
56
|
+
name: sectionName,
|
|
57
|
+
source: section?.source || null,
|
|
58
|
+
content: lines.join('\n'),
|
|
59
|
+
lines,
|
|
60
|
+
diagnostics,
|
|
61
|
+
};
|
|
62
|
+
});
|
|
47
63
|
}
|
|
@@ -29,19 +29,55 @@ export function opaqueStackSections(sections, knownNames) {
|
|
|
29
29
|
}));
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function requirementClosures(components) {
|
|
33
|
+
const byId = new Map(components.map((component) => [component.id, component]));
|
|
34
|
+
const closures = new Map();
|
|
35
|
+
const closure = (id) => {
|
|
36
|
+
if (closures.has(id)) return closures.get(id);
|
|
37
|
+
const required = new Set();
|
|
38
|
+
closures.set(id, required);
|
|
39
|
+
for (const dependency of byId.get(id)?.requires || []) {
|
|
40
|
+
if (!byId.has(dependency) || dependency === id) continue;
|
|
41
|
+
required.add(dependency);
|
|
42
|
+
for (const transitive of closure(dependency)) required.add(transitive);
|
|
43
|
+
}
|
|
44
|
+
return required;
|
|
45
|
+
};
|
|
46
|
+
for (const { id } of components) closure(id);
|
|
47
|
+
return closures;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function quotedProposal({ source, lines }) {
|
|
51
|
+
return [`- \`${source}\` proposes:`, '', ' ~~~markdown', ...lines.map((line) => (line ? ` ${line}` : '')), ' ~~~'];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Render the actionable decision message for one unresolved section. */
|
|
55
|
+
export function unresolvedStackSectionMessage(name, proposals) {
|
|
56
|
+
return [
|
|
57
|
+
`Selected Stack components propose competing \`## ${name}\` sections and none supersedes the others: ${proposals.map(({ source }) => source).join(', ')}.`,
|
|
58
|
+
`Author one complete project \`## ${name}\` section in genesis/stack.md that chooses between these proposals; Genesis never merges them or picks one.`,
|
|
59
|
+
'',
|
|
60
|
+
...proposals.flatMap(quotedProposal),
|
|
61
|
+
].join('\n');
|
|
62
|
+
}
|
|
63
|
+
|
|
32
64
|
/**
|
|
33
65
|
* Compose consumer-owned Stack sections without interpreting their contents.
|
|
34
|
-
* An existing project declaration wins
|
|
35
|
-
*
|
|
66
|
+
* An existing project declaration wins. Otherwise a component's proposal
|
|
67
|
+
* supersedes the proposals of selected components it transitively requires;
|
|
68
|
+
* exactly one remaining proposal is materialized, and several remaining
|
|
69
|
+
* proposals are an unresolved project decision.
|
|
36
70
|
*/
|
|
37
71
|
export function composeStackSections(components = [], projectSections = []) {
|
|
38
72
|
const projectByName = new Map(projectSections.map((section) => [section.name, section]));
|
|
73
|
+
const closures = requirementClosures(components);
|
|
39
74
|
const componentByName = new Map();
|
|
40
75
|
for (const component of components) {
|
|
41
76
|
for (const section of component.extensions || []) {
|
|
42
77
|
const declarations = componentByName.get(section.name) || [];
|
|
43
78
|
declarations.push({
|
|
44
79
|
...section,
|
|
80
|
+
component: component.id,
|
|
45
81
|
source: `component:${component.id}`,
|
|
46
82
|
});
|
|
47
83
|
componentByName.set(section.name, declarations);
|
|
@@ -59,23 +95,29 @@ export function composeStackSections(components = [], projectSections = []) {
|
|
|
59
95
|
};
|
|
60
96
|
}
|
|
61
97
|
const declarations = componentByName.get(name) || [];
|
|
62
|
-
|
|
98
|
+
const remaining = declarations.filter(({ component }) => !declarations.some((other) => (
|
|
99
|
+
other.component !== component && closures.get(other.component)?.has(component)
|
|
100
|
+
)));
|
|
101
|
+
if (remaining.length === 1) {
|
|
63
102
|
return {
|
|
64
103
|
name,
|
|
65
|
-
lines: [...
|
|
66
|
-
source:
|
|
104
|
+
lines: [...remaining[0].lines],
|
|
105
|
+
source: remaining[0].source,
|
|
67
106
|
diagnostics: [],
|
|
68
107
|
};
|
|
69
108
|
}
|
|
70
|
-
const
|
|
109
|
+
const proposals = remaining
|
|
110
|
+
.map(({ source, lines }) => ({ source, lines: trimStackSectionLines(lines) }))
|
|
111
|
+
.sort((left, right) => left.source.localeCompare(right.source));
|
|
112
|
+
const sources = proposals.map(({ source }) => source);
|
|
71
113
|
return {
|
|
72
114
|
name,
|
|
73
115
|
lines: [],
|
|
74
116
|
source: null,
|
|
75
117
|
diagnostics: [{
|
|
76
118
|
code: 'STACK_SECTION_AMBIGUOUS',
|
|
77
|
-
message:
|
|
78
|
-
details: { name, sources },
|
|
119
|
+
message: unresolvedStackSectionMessage(name, proposals),
|
|
120
|
+
details: { name, sources, proposals },
|
|
79
121
|
}],
|
|
80
122
|
};
|
|
81
123
|
});
|
package/src/index/stack.js
CHANGED
|
@@ -230,7 +230,11 @@ export async function addStackPieces({ pieces, projectRoot, stackPackages = [] }
|
|
|
230
230
|
});
|
|
231
231
|
const stackChanged = rendered !== source;
|
|
232
232
|
if (stackChanged) await writeFileAtomic(location, rendered);
|
|
233
|
-
const stack = await readStack(projectRoot, {
|
|
233
|
+
const stack = await readStack(projectRoot, {
|
|
234
|
+
allowUnresolvedSections: true,
|
|
235
|
+
stackPackages: availablePackages,
|
|
236
|
+
});
|
|
237
|
+
const { unresolvedSections } = stack;
|
|
234
238
|
const skills = await syncProjectSkills({
|
|
235
239
|
projectRoot,
|
|
236
240
|
stack,
|
|
@@ -246,7 +250,10 @@ export async function addStackPieces({ pieces, projectRoot, stackPackages = [] }
|
|
|
246
250
|
: 'Every requested Stack component and Agent Skill is already selected.',
|
|
247
251
|
components: selected,
|
|
248
252
|
changedFiles,
|
|
249
|
-
diagnostics:
|
|
253
|
+
diagnostics: [
|
|
254
|
+
...unresolvedSections.map(({ diagnostic }) => diagnostic),
|
|
255
|
+
...skills.diagnostics,
|
|
256
|
+
],
|
|
250
257
|
prompt: renderStackPreparationPrompt({
|
|
251
258
|
components: stack.components,
|
|
252
259
|
materializedSections: projectContracts.materializedSections,
|
|
@@ -254,6 +261,7 @@ export async function addStackPieces({ pieces, projectRoot, stackPackages = [] }
|
|
|
254
261
|
projectContracts: stack.projectContracts,
|
|
255
262
|
reviewSections: [...new Set(newlySelectedPieces.flatMap(stackPieceContractNames))]
|
|
256
263
|
.filter((name) => sections.has(name)),
|
|
264
|
+
unresolvedSections,
|
|
257
265
|
}),
|
|
258
266
|
};
|
|
259
267
|
}
|
|
@@ -295,6 +303,7 @@ async function customizePiece(projectRoot, piece) {
|
|
|
295
303
|
|
|
296
304
|
async function readStackWithContractMode(projectRoot, {
|
|
297
305
|
allowComponentContracts,
|
|
306
|
+
allowUnresolvedSections = false,
|
|
298
307
|
stackPackages = [],
|
|
299
308
|
}) {
|
|
300
309
|
const location = path.join(projectRoot, STACK_PATH);
|
|
@@ -324,6 +333,7 @@ async function readStackWithContractMode(projectRoot, {
|
|
|
324
333
|
const projectContracts = (allowComponentContracts
|
|
325
334
|
? materializeStackProjectContracts
|
|
326
335
|
: requireMaterializedStackProjectContracts)({
|
|
336
|
+
allowUnresolvedSections,
|
|
327
337
|
components,
|
|
328
338
|
knownSectionNames: STACK_SECTIONS,
|
|
329
339
|
sections,
|
|
@@ -386,6 +396,7 @@ async function readStackWithContractMode(projectRoot, {
|
|
|
386
396
|
environmentFiles,
|
|
387
397
|
extensions,
|
|
388
398
|
projectContracts: projectContracts.projectContracts,
|
|
399
|
+
...(allowUnresolvedSections ? { unresolvedSections: projectContracts.unresolvedSections } : {}),
|
|
389
400
|
resources,
|
|
390
401
|
guidance: composedProse(components, 'guidance'),
|
|
391
402
|
adoption: composedProse(components, 'adoption'),
|
|
@@ -394,13 +405,32 @@ async function readStackWithContractMode(projectRoot, {
|
|
|
394
405
|
};
|
|
395
406
|
}
|
|
396
407
|
|
|
397
|
-
|
|
408
|
+
/**
|
|
409
|
+
* Read the current-format Stack. Only operations that report pending project
|
|
410
|
+
* decisions themselves (selection and migration) may allow unresolved
|
|
411
|
+
* competing sections; the returned Stack then lists them.
|
|
412
|
+
*/
|
|
413
|
+
export function readStack(projectRoot, { allowUnresolvedSections = false, stackPackages = [] } = {}) {
|
|
398
414
|
return readStackWithContractMode(projectRoot, {
|
|
399
415
|
allowComponentContracts: false,
|
|
416
|
+
allowUnresolvedSections,
|
|
400
417
|
stackPackages,
|
|
401
418
|
});
|
|
402
419
|
}
|
|
403
420
|
|
|
421
|
+
/** Diagnostics and the preparation prompt for a Stack read with unresolved sections allowed. */
|
|
422
|
+
export function stackDecisionResult(stack) {
|
|
423
|
+
const unresolvedSections = stack.unresolvedSections || [];
|
|
424
|
+
return {
|
|
425
|
+
diagnostics: unresolvedSections.map(({ diagnostic }) => diagnostic),
|
|
426
|
+
prompt: renderStackPreparationPrompt({
|
|
427
|
+
components: stack.components,
|
|
428
|
+
projectContracts: stack.projectContracts,
|
|
429
|
+
unresolvedSections,
|
|
430
|
+
}),
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
404
434
|
export function readLegacyStack(projectRoot, { stackPackages = [] } = {}) {
|
|
405
435
|
return readStackWithContractMode(projectRoot, {
|
|
406
436
|
allowComponentContracts: true,
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
3
|
|
|
3
4
|
import { checkProject } from './index/check.js';
|
|
4
5
|
import { inspectProjectSkills, syncProjectSkills } from './index/agent-skills.js';
|
|
@@ -21,7 +22,7 @@ import { initializeProject } from './index/init.js';
|
|
|
21
22
|
import { migrateProject } from './index/migration.js';
|
|
22
23
|
import { installCodexPlugin } from './index/codex-plugin.js';
|
|
23
24
|
import { inspectProjectEnvironment } from './index/environment-files.js';
|
|
24
|
-
import { inspectProjectStackSection } from './index/stack-section-inspection.js';
|
|
25
|
+
import { inspectProjectStackSection, inspectProjectStackSections } from './index/stack-section-inspection.js';
|
|
25
26
|
import { listStackCatalogPieces } from './index/stack-catalog.js';
|
|
26
27
|
import { addStackPieces, readStack } from './index/stack.js';
|
|
27
28
|
import { uniqueSorted } from './index/utils.js';
|
|
@@ -84,14 +85,16 @@ export async function initialize({ projectRoot = process.cwd(), stackPackages =
|
|
|
84
85
|
|
|
85
86
|
export async function migrate({ projectRoot = process.cwd(), stackPackages = [] } = {}) {
|
|
86
87
|
const migrated = await migrateProject({ projectRoot, stackPackages });
|
|
87
|
-
const index = await buildProjectIndex({ projectRoot, stackPackages });
|
|
88
|
+
const index = await buildProjectIndex({ allowUnresolvedSections: true, projectRoot, stackPackages });
|
|
88
89
|
const result = withIndexResult(migrated, index);
|
|
89
90
|
const checked = await checkProject({ projectRoot, stackPackages });
|
|
90
91
|
return {
|
|
91
92
|
...result,
|
|
92
93
|
projectFormat: checked.projectFormat,
|
|
93
94
|
check: checked,
|
|
94
|
-
diagnostics: [...result.diagnostics, ...checked.diagnostics],
|
|
95
|
+
diagnostics: [...result.diagnostics, ...checked.diagnostics].filter((diagnostic, index, all) => (
|
|
96
|
+
!all.slice(0, index).some((previous) => isDeepStrictEqual(previous, diagnostic))
|
|
97
|
+
)),
|
|
95
98
|
guidance: checked.guidance,
|
|
96
99
|
};
|
|
97
100
|
}
|
|
@@ -127,7 +130,11 @@ export function installCodex({ environment = process.env } = {}) {
|
|
|
127
130
|
export async function addStack({ pieces, projectRoot = process.cwd(), stackPackages = [] } = {}) {
|
|
128
131
|
const root = path.resolve(projectRoot);
|
|
129
132
|
const selected = await addStackPieces({ pieces, projectRoot: root, stackPackages });
|
|
130
|
-
const index = await buildProjectIndex({
|
|
133
|
+
const index = await buildProjectIndex({
|
|
134
|
+
allowUnresolvedSections: true,
|
|
135
|
+
projectRoot: root,
|
|
136
|
+
stackPackages,
|
|
137
|
+
});
|
|
131
138
|
return withIndexResult(selected, index);
|
|
132
139
|
}
|
|
133
140
|
|
|
@@ -161,6 +168,10 @@ export function inspectStackSection(options) {
|
|
|
161
168
|
return inspectProjectStackSection(options);
|
|
162
169
|
}
|
|
163
170
|
|
|
171
|
+
export function inspectStackSections(options) {
|
|
172
|
+
return inspectProjectStackSections(options);
|
|
173
|
+
}
|
|
174
|
+
|
|
164
175
|
export function listEngineeringProfiles() {
|
|
165
176
|
return listEngineeringProfileCatalog();
|
|
166
177
|
}
|