@fission-ai/openspec 1.4.1 → 1.6.0-beta.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 +37 -4
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +95 -95
- package/dist/commands/change.d.ts +5 -2
- package/dist/commands/change.js +23 -55
- package/dist/commands/config.js +6 -65
- package/dist/commands/context.d.ts +3 -0
- package/dist/commands/context.js +155 -0
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.js +163 -0
- package/dist/commands/shared-gather.d.ts +14 -0
- package/dist/commands/shared-gather.js +31 -0
- package/dist/commands/shared-output.d.ts +18 -0
- package/dist/commands/shared-output.js +61 -0
- package/dist/commands/show.d.ts +11 -6
- package/dist/commands/show.js +70 -25
- package/dist/commands/spec.d.ts +5 -1
- package/dist/commands/spec.js +15 -4
- package/dist/commands/store.d.ts +3 -0
- package/dist/commands/store.js +547 -0
- package/dist/commands/validate.d.ts +10 -0
- package/dist/commands/validate.js +88 -40
- package/dist/commands/workflow/index.d.ts +0 -2
- package/dist/commands/workflow/index.js +0 -1
- package/dist/commands/workflow/instructions.d.ts +11 -1
- package/dist/commands/workflow/instructions.js +71 -22
- package/dist/commands/workflow/new-change.d.ts +6 -3
- package/dist/commands/workflow/new-change.js +42 -77
- package/dist/commands/workflow/shared.d.ts +15 -3
- package/dist/commands/workflow/shared.js +20 -3
- package/dist/commands/workflow/status.d.ts +2 -0
- package/dist/commands/workflow/status.js +19 -17
- package/dist/commands/workset-input.d.ts +19 -0
- package/dist/commands/workset-input.js +112 -0
- package/dist/commands/workset-prompts.d.ts +12 -0
- package/dist/commands/workset-prompts.js +143 -0
- package/dist/commands/workset.d.ts +25 -0
- package/dist/commands/workset.js +446 -0
- package/dist/core/archive.d.ts +17 -6
- package/dist/core/archive.js +220 -68
- package/dist/core/artifact-graph/index.d.ts +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +18 -13
- package/dist/core/artifact-graph/instruction-loader.js +8 -31
- package/dist/core/change-metadata/schema.d.ts +1 -0
- package/dist/core/change-metadata/schema.js +4 -2
- package/dist/core/change-status-policy.d.ts +5 -18
- package/dist/core/change-status-policy.js +2 -37
- package/dist/core/command-generation/adapters/bob.js +1 -14
- package/dist/core/command-generation/adapters/claude.d.ts +1 -1
- package/dist/core/command-generation/adapters/claude.js +4 -15
- package/dist/core/command-generation/adapters/cursor.js +1 -14
- package/dist/core/command-generation/adapters/index.d.ts +2 -0
- package/dist/core/command-generation/adapters/index.js +2 -0
- package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
- package/dist/core/command-generation/adapters/oh-my-pi.js +44 -0
- package/dist/core/command-generation/adapters/pi.js +1 -14
- package/dist/core/command-generation/adapters/trae.d.ts +13 -0
- package/dist/core/command-generation/adapters/trae.js +48 -0
- package/dist/core/command-generation/adapters/windsurf.js +1 -14
- package/dist/core/command-generation/registry.js +4 -0
- package/dist/core/command-generation/yaml.d.ts +22 -0
- package/dist/core/command-generation/yaml.js +38 -0
- package/dist/core/completions/command-registry.js +87 -289
- package/dist/core/completions/generators/zsh-generator.js +3 -1
- package/dist/core/completions/installers/bash-installer.js +3 -0
- package/dist/core/completions/installers/fish-installer.js +8 -0
- package/dist/core/completions/installers/powershell-installer.js +30 -2
- package/dist/core/completions/installers/zsh-installer.js +3 -0
- package/dist/core/completions/shared-flags.d.ts +1 -0
- package/dist/core/completions/shared-flags.js +5 -0
- package/dist/core/config-schema.d.ts +2 -1
- package/dist/core/config-schema.js +26 -0
- package/dist/core/config.js +1 -0
- package/dist/core/file-state.d.ts +36 -0
- package/dist/core/file-state.js +112 -0
- package/dist/core/global-config.d.ts +2 -0
- package/dist/core/id.d.ts +17 -0
- package/dist/core/id.js +30 -0
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +3 -3
- package/dist/core/init.js +27 -6
- package/dist/core/list.d.ts +2 -0
- package/dist/core/list.js +37 -14
- package/dist/core/openers.d.ts +77 -0
- package/dist/core/openers.js +251 -0
- package/dist/core/openspec-root.d.ts +45 -0
- package/dist/core/openspec-root.js +195 -0
- package/dist/core/parsers/change-parser.js +2 -1
- package/dist/core/parsers/markdown-parser.d.ts +0 -3
- package/dist/core/parsers/markdown-parser.js +5 -59
- package/dist/core/parsers/requirement-blocks.d.ts +11 -0
- package/dist/core/parsers/requirement-blocks.js +42 -8
- package/dist/core/parsers/requirement-text.d.ts +55 -0
- package/dist/core/parsers/requirement-text.js +136 -0
- package/dist/core/planning-home.d.ts +1 -6
- package/dist/core/planning-home.js +2 -43
- package/dist/core/profile-sync-drift.js +1 -0
- package/dist/core/profiles.d.ts +2 -2
- package/dist/core/profiles.js +2 -1
- package/dist/core/project-config.d.ts +44 -1
- package/dist/core/project-config.js +160 -10
- package/dist/core/references.d.ts +63 -0
- package/dist/core/references.js +310 -0
- package/dist/core/relationship-health.d.ts +65 -0
- package/dist/core/relationship-health.js +64 -0
- package/dist/core/root-selection.d.ts +122 -0
- package/dist/core/root-selection.js +337 -0
- package/dist/core/schemas/base.schema.js +7 -2
- package/dist/core/shared/allowed-tools.d.ts +12 -0
- package/dist/core/shared/allowed-tools.js +12 -0
- package/dist/core/shared/skill-generation.js +5 -1
- package/dist/core/shared/tool-detection.d.ts +2 -2
- package/dist/core/shared/tool-detection.js +2 -0
- package/dist/core/specs-apply.d.ts +6 -1
- package/dist/core/specs-apply.js +41 -5
- package/dist/core/store/errors.d.ts +20 -0
- package/dist/core/{context-store → store}/errors.js +3 -3
- package/dist/core/store/foundation.d.ts +56 -0
- package/dist/core/store/foundation.js +251 -0
- package/dist/core/store/git.d.ts +23 -0
- package/dist/core/store/git.js +137 -0
- package/dist/core/{context-store → store}/index.d.ts +0 -1
- package/dist/core/{context-store → store}/index.js +0 -1
- package/dist/core/store/operations.d.ts +114 -0
- package/dist/core/store/operations.js +803 -0
- package/dist/core/store/registry.d.ts +58 -0
- package/dist/core/store/registry.js +275 -0
- package/dist/core/templates/skill-templates.d.ts +1 -0
- package/dist/core/templates/skill-templates.js +1 -0
- package/dist/core/templates/workflows/apply-change.js +5 -4
- package/dist/core/templates/workflows/archive-change.js +5 -4
- package/dist/core/templates/workflows/bulk-archive-change.js +5 -4
- package/dist/core/templates/workflows/continue-change.js +7 -2
- package/dist/core/templates/workflows/explore.js +5 -0
- package/dist/core/templates/workflows/ff-change.js +5 -0
- package/dist/core/templates/workflows/new-change.js +5 -0
- package/dist/core/templates/workflows/onboard.js +4 -1
- package/dist/core/templates/workflows/propose.js +5 -0
- package/dist/core/templates/workflows/store-selection.d.ts +8 -0
- package/dist/core/templates/workflows/store-selection.js +8 -0
- package/dist/core/templates/workflows/sync-specs.js +5 -4
- package/dist/core/templates/workflows/update-change.d.ts +10 -0
- package/dist/core/templates/workflows/update-change.js +167 -0
- package/dist/core/templates/workflows/verify-change.js +5 -4
- package/dist/core/update.js +4 -4
- package/dist/core/validation/validator.d.ts +7 -0
- package/dist/core/validation/validator.js +101 -38
- package/dist/core/view.js +1 -1
- package/dist/core/working-set.d.ts +47 -0
- package/dist/core/working-set.js +43 -0
- package/dist/core/worksets.d.ts +75 -0
- package/dist/core/worksets.js +245 -0
- package/dist/core/zod-issues.d.ts +4 -0
- package/dist/core/zod-issues.js +10 -0
- package/dist/utils/change-metadata.d.ts +1 -0
- package/dist/utils/change-metadata.js +1 -1
- package/dist/utils/change-utils.js +15 -0
- package/dist/utils/file-system.js +36 -17
- package/dist/utils/task-progress.d.ts +10 -1
- package/dist/utils/task-progress.js +66 -2
- package/package.json +26 -23
- package/dist/commands/context-store.d.ts +0 -3
- package/dist/commands/context-store.js +0 -475
- package/dist/commands/initiative.d.ts +0 -13
- package/dist/commands/initiative.js +0 -318
- package/dist/commands/workflow/initiative-link.d.ts +0 -24
- package/dist/commands/workflow/initiative-link.js +0 -47
- package/dist/commands/workflow/set-change.d.ts +0 -13
- package/dist/commands/workflow/set-change.js +0 -87
- package/dist/commands/workspace/context-status.d.ts +0 -4
- package/dist/commands/workspace/context-status.js +0 -59
- package/dist/commands/workspace/open-target-selection.d.ts +0 -13
- package/dist/commands/workspace/open-target-selection.js +0 -146
- package/dist/commands/workspace/open-view.d.ts +0 -62
- package/dist/commands/workspace/open-view.js +0 -249
- package/dist/commands/workspace/open.d.ts +0 -37
- package/dist/commands/workspace/open.js +0 -110
- package/dist/commands/workspace/opener-selection.d.ts +0 -11
- package/dist/commands/workspace/opener-selection.js +0 -98
- package/dist/commands/workspace/operations.d.ts +0 -29
- package/dist/commands/workspace/operations.js +0 -543
- package/dist/commands/workspace/prompt-theme.d.ts +0 -29
- package/dist/commands/workspace/prompt-theme.js +0 -24
- package/dist/commands/workspace/registration.d.ts +0 -13
- package/dist/commands/workspace/registration.js +0 -84
- package/dist/commands/workspace/selection.d.ts +0 -8
- package/dist/commands/workspace/selection.js +0 -104
- package/dist/commands/workspace/setup-prompts.d.ts +0 -13
- package/dist/commands/workspace/setup-prompts.js +0 -121
- package/dist/commands/workspace/types.d.ts +0 -103
- package/dist/commands/workspace/types.js +0 -36
- package/dist/commands/workspace.d.ts +0 -5
- package/dist/commands/workspace.js +0 -574
- package/dist/core/collections/index.d.ts +0 -3
- package/dist/core/collections/index.js +0 -3
- package/dist/core/collections/initiatives/collection.d.ts +0 -4
- package/dist/core/collections/initiatives/collection.js +0 -17
- package/dist/core/collections/initiatives/index.d.ts +0 -6
- package/dist/core/collections/initiatives/index.js +0 -6
- package/dist/core/collections/initiatives/operations.d.ts +0 -49
- package/dist/core/collections/initiatives/operations.js +0 -175
- package/dist/core/collections/initiatives/resolution.d.ts +0 -87
- package/dist/core/collections/initiatives/resolution.js +0 -374
- package/dist/core/collections/initiatives/schema.d.ts +0 -41
- package/dist/core/collections/initiatives/schema.js +0 -134
- package/dist/core/collections/initiatives/templates.d.ts +0 -12
- package/dist/core/collections/initiatives/templates.js +0 -90
- package/dist/core/collections/runtime.d.ts +0 -46
- package/dist/core/collections/runtime.js +0 -194
- package/dist/core/context-store/binding.d.ts +0 -53
- package/dist/core/context-store/binding.js +0 -197
- package/dist/core/context-store/errors.d.ts +0 -20
- package/dist/core/context-store/foundation.d.ts +0 -55
- package/dist/core/context-store/foundation.js +0 -321
- package/dist/core/context-store/operations.d.ts +0 -85
- package/dist/core/context-store/operations.js +0 -528
- package/dist/core/context-store/registry.d.ts +0 -45
- package/dist/core/context-store/registry.js +0 -229
- package/dist/core/workspace/foundation.d.ts +0 -67
- package/dist/core/workspace/foundation.js +0 -295
- package/dist/core/workspace/index.d.ts +0 -8
- package/dist/core/workspace/index.js +0 -8
- package/dist/core/workspace/legacy-state.d.ts +0 -28
- package/dist/core/workspace/legacy-state.js +0 -200
- package/dist/core/workspace/link-input.d.ts +0 -9
- package/dist/core/workspace/link-input.js +0 -32
- package/dist/core/workspace/open-surface.d.ts +0 -45
- package/dist/core/workspace/open-surface.js +0 -215
- package/dist/core/workspace/openers.d.ts +0 -21
- package/dist/core/workspace/openers.js +0 -124
- package/dist/core/workspace/registry.d.ts +0 -24
- package/dist/core/workspace/registry.js +0 -146
- package/dist/core/workspace/skills.d.ts +0 -57
- package/dist/core/workspace/skills.js +0 -334
- package/dist/core/workspace/state-io.d.ts +0 -10
- package/dist/core/workspace/state-io.js +0 -121
- package/schemas/workspace-planning/schema.yaml +0 -72
- package/schemas/workspace-planning/templates/design.md +0 -33
- package/schemas/workspace-planning/templates/proposal.md +0 -28
- package/schemas/workspace-planning/templates/spec.md +0 -9
- package/schemas/workspace-planning/templates/tasks.md +0 -15
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'fs';
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { parse as parseYaml } from 'yaml';
|
|
4
4
|
import { z } from 'zod';
|
|
@@ -34,8 +34,77 @@ export const ProjectConfigSchema = z.object({
|
|
|
34
34
|
)
|
|
35
35
|
.optional()
|
|
36
36
|
.describe('Per-artifact rules, keyed by artifact ID'),
|
|
37
|
+
// Note: the `references` field (id strings or {id, remote} maps) is
|
|
38
|
+
// deliberately absent here — readProjectConfig parses and normalizes
|
|
39
|
+
// it by hand (see DeclarationEntry below); a schema entry nothing
|
|
40
|
+
// parses would only drift from the real behavior.
|
|
41
|
+
// Optional: the declared default store. Only consulted by root
|
|
42
|
+
// resolution when this openspec/ directory is config-only (no specs/
|
|
43
|
+
// or changes/); a fallback, never an override.
|
|
44
|
+
store: z
|
|
45
|
+
.string()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe('Store id used as the OpenSpec root when no local planning shape exists'),
|
|
37
48
|
});
|
|
38
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Parser for `references:` declarations: string entries or
|
|
51
|
+
* {id, remote} maps, normalized to DeclarationEntry[]. Dedup keys on
|
|
52
|
+
* id and keeps the first position; the first entry carrying a remote
|
|
53
|
+
* supplies it (a later duplicate fills a missing remote, never
|
|
54
|
+
* overrides). Invalid entries drop with a warning like other resilient
|
|
55
|
+
* fields; returns undefined when the field is absent or normalizes to
|
|
56
|
+
* empty.
|
|
57
|
+
*/
|
|
58
|
+
function parseDeclarationList(raw) {
|
|
59
|
+
const fieldName = 'references';
|
|
60
|
+
if (raw === undefined) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
if (!Array.isArray(raw)) {
|
|
64
|
+
console.warn(`Invalid '${fieldName}' field in config (must be an array of store ids)`);
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const byId = new Map();
|
|
68
|
+
let droppedEntries = false;
|
|
69
|
+
let droppedRemotes = false;
|
|
70
|
+
for (const entry of raw) {
|
|
71
|
+
let declaration = null;
|
|
72
|
+
if (typeof entry === 'string') {
|
|
73
|
+
declaration = { id: entry };
|
|
74
|
+
}
|
|
75
|
+
else if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
|
|
76
|
+
const candidate = entry;
|
|
77
|
+
if (typeof candidate.id === 'string') {
|
|
78
|
+
declaration = { id: candidate.id };
|
|
79
|
+
if (typeof candidate.remote === 'string' && candidate.remote.length > 0) {
|
|
80
|
+
declaration.remote = candidate.remote;
|
|
81
|
+
}
|
|
82
|
+
else if (candidate.remote !== undefined) {
|
|
83
|
+
droppedRemotes = true; // remote dropped, id kept
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!declaration) {
|
|
88
|
+
droppedEntries = true;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const existing = byId.get(declaration.id);
|
|
92
|
+
if (!existing) {
|
|
93
|
+
byId.set(declaration.id, declaration);
|
|
94
|
+
}
|
|
95
|
+
else if (existing.remote === undefined && declaration.remote !== undefined) {
|
|
96
|
+
existing.remote = declaration.remote;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (droppedEntries) {
|
|
100
|
+
console.warn(`Some '${fieldName}' entries are invalid, ignoring them`);
|
|
101
|
+
}
|
|
102
|
+
if (droppedRemotes) {
|
|
103
|
+
console.warn(`Some '${fieldName}' remotes are not non-empty strings; the ids are kept without a clone source`);
|
|
104
|
+
}
|
|
105
|
+
return byId.size > 0 ? [...byId.values()] : undefined;
|
|
106
|
+
}
|
|
107
|
+
export const MAX_CONTEXT_SIZE = 50 * 1024; // 50KB hard limit, shared with the references index
|
|
39
108
|
/**
|
|
40
109
|
* Read and parse openspec/config.yaml from project root.
|
|
41
110
|
* Uses resilient parsing - validates each field independently using Zod safeParse.
|
|
@@ -56,13 +125,9 @@ const MAX_CONTEXT_SIZE = 50 * 1024; // 50KB hard limit
|
|
|
56
125
|
* @returns Parsed config or null if file doesn't exist
|
|
57
126
|
*/
|
|
58
127
|
export function readProjectConfig(projectRoot) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
configPath = path.join(projectRoot, 'openspec', 'config.yml');
|
|
63
|
-
if (!existsSync(configPath)) {
|
|
64
|
-
return null; // No config is OK
|
|
65
|
-
}
|
|
128
|
+
const configPath = resolveConfigFilePath(projectRoot);
|
|
129
|
+
if (configPath === null) {
|
|
130
|
+
return null; // No config is OK
|
|
66
131
|
}
|
|
67
132
|
try {
|
|
68
133
|
const content = readFileSync(configPath, 'utf-8');
|
|
@@ -131,14 +196,32 @@ export function readProjectConfig(projectRoot) {
|
|
|
131
196
|
console.warn(`Invalid 'rules' field in config (must be object)`);
|
|
132
197
|
}
|
|
133
198
|
}
|
|
199
|
+
const references = parseDeclarationList(raw.references);
|
|
200
|
+
if (references) {
|
|
201
|
+
config.references = references;
|
|
202
|
+
}
|
|
203
|
+
// Parse store pointer field: a string, or dropped with a warning.
|
|
204
|
+
// (Root resolution does NOT use this parse — it uses readStorePointer
|
|
205
|
+
// below, which errors on malformed pointers instead of dropping.)
|
|
206
|
+
if (raw.store !== undefined) {
|
|
207
|
+
if (typeof raw.store === 'string') {
|
|
208
|
+
config.store = raw.store;
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
console.warn(`Warning: ignoring invalid store: field in ${configPathForWarnings(projectRoot)} (must be a single store id string).`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
134
214
|
// Return partial config even if some fields failed
|
|
135
215
|
return Object.keys(config).length > 0 ? config : null;
|
|
136
216
|
}
|
|
137
217
|
catch (error) {
|
|
138
|
-
console.warn(`
|
|
218
|
+
console.warn(`Warning: could not parse ${configPathForWarnings(projectRoot)} (${error instanceof Error ? error.message.split('\n')[0] : String(error)}); ignoring it.`);
|
|
139
219
|
return null;
|
|
140
220
|
}
|
|
141
221
|
}
|
|
222
|
+
function configPathForWarnings(projectRoot) {
|
|
223
|
+
return resolveConfigFilePath(projectRoot) ?? path.join(projectRoot, 'openspec', 'config.yaml');
|
|
224
|
+
}
|
|
142
225
|
/**
|
|
143
226
|
* Validate artifact IDs in rules against a schema's artifacts.
|
|
144
227
|
* Called during instruction loading (when schema is known).
|
|
@@ -220,4 +303,71 @@ export function suggestSchemas(invalidSchemaName, availableSchemas) {
|
|
|
220
303
|
message += `\nFix: Edit openspec/config.yaml and change 'schema: ${invalidSchemaName}' to a valid schema name`;
|
|
221
304
|
return message;
|
|
222
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Warning-silent targeted read of the `store:` pointer. Used by root
|
|
308
|
+
* resolution (which must not re-emit the resilient parser's field
|
|
309
|
+
* warnings) and by `openspec init`'s pointer guard. Unlike
|
|
310
|
+
* `readProjectConfig`, a malformed value is REPORTED, not dropped —
|
|
311
|
+
* a dropped pointer would silently flip where work lands.
|
|
312
|
+
*/
|
|
313
|
+
export function readStorePointer(projectRoot) {
|
|
314
|
+
const configPath = resolveConfigFilePath(projectRoot);
|
|
315
|
+
if (configPath === null) {
|
|
316
|
+
return { filePath: null };
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
const raw = parseYaml(readFileSync(configPath, 'utf-8'));
|
|
320
|
+
// Empty, comments-only, or non-mapping configs carry no pointer;
|
|
321
|
+
// they are imperfect, not malformed (readProjectConfig owns the
|
|
322
|
+
// field warnings for those).
|
|
323
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
324
|
+
return { filePath: configPath };
|
|
325
|
+
}
|
|
326
|
+
const value = raw.store;
|
|
327
|
+
if (value === undefined) {
|
|
328
|
+
return { filePath: configPath };
|
|
329
|
+
}
|
|
330
|
+
if (typeof value === 'string') {
|
|
331
|
+
return { value, filePath: configPath };
|
|
332
|
+
}
|
|
333
|
+
return { malformed: 'non_string', filePath: configPath };
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
return { malformed: 'unparseable', filePath: configPath };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/** Shared .yaml/.yml probe used by readProjectConfig and readStorePointer. */
|
|
340
|
+
export function resolveConfigFilePath(projectRoot) {
|
|
341
|
+
const yamlPath = path.join(projectRoot, 'openspec', 'config.yaml');
|
|
342
|
+
if (existsSync(yamlPath)) {
|
|
343
|
+
return yamlPath;
|
|
344
|
+
}
|
|
345
|
+
const ymlPath = path.join(projectRoot, 'openspec', 'config.yml');
|
|
346
|
+
return existsSync(ymlPath) ? ymlPath : null;
|
|
347
|
+
}
|
|
348
|
+
/** Human rendering of a malformed pointer reason, shared by every surface. */
|
|
349
|
+
export function storePointerProblem(reason) {
|
|
350
|
+
return reason === 'unparseable'
|
|
351
|
+
? 'the config file could not be read as YAML'
|
|
352
|
+
: 'the store key must be a single store id string';
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* One classification for "real root vs config-only pointer dir", shared
|
|
356
|
+
* by root resolution and the init pointer guard so they can never
|
|
357
|
+
* disagree (slice 3.2).
|
|
358
|
+
*/
|
|
359
|
+
export function classifyOpenSpecDir(projectRoot) {
|
|
360
|
+
const openspecDir = path.join(projectRoot, 'openspec');
|
|
361
|
+
const hasPlanningShape = isDirectorySync(path.join(openspecDir, 'specs')) ||
|
|
362
|
+
isDirectorySync(path.join(openspecDir, 'changes'));
|
|
363
|
+
return { hasPlanningShape, pointer: readStorePointer(projectRoot) };
|
|
364
|
+
}
|
|
365
|
+
function isDirectorySync(candidatePath) {
|
|
366
|
+
try {
|
|
367
|
+
return statSync(candidatePath).isDirectory();
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
223
373
|
//# sourceMappingURL=project-config.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type StoreDiagnostic } from './store/errors.js';
|
|
2
|
+
import { listStoreRegistryEntries } from './store/foundation.js';
|
|
3
|
+
import { type ResolvedOpenSpecRoot } from './root-selection.js';
|
|
4
|
+
import { type DeclarationEntry } from './project-config.js';
|
|
5
|
+
export interface ReferenceSpecEntry {
|
|
6
|
+
id: string;
|
|
7
|
+
summary: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ReferenceIndexEntry {
|
|
10
|
+
store_id: string;
|
|
11
|
+
root?: string;
|
|
12
|
+
specs?: ReferenceSpecEntry[];
|
|
13
|
+
fetch?: string;
|
|
14
|
+
status: StoreDiagnostic[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Tolerant first-Purpose-line extraction. parseSpec() throws on specs
|
|
18
|
+
* without Purpose/Requirements sections; the index must never fail on an
|
|
19
|
+
* imperfect upstream spec, so this scans for the heading directly —
|
|
20
|
+
* fence-aware, so `## Purpose` inside a code block never matches, and
|
|
21
|
+
* tolerant of CommonMark closing hashes (`## Purpose ##`).
|
|
22
|
+
*/
|
|
23
|
+
export declare function extractFirstPurposeLine(markdown: string): string;
|
|
24
|
+
export declare function fetchRecipe(storeId: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Pure renderer for the artifact-instructions XML block. Also the byte
|
|
27
|
+
* budget's measuring stick (it is the larger rendering).
|
|
28
|
+
*/
|
|
29
|
+
export declare function renderReferencedStoresBlock(entries: ReferenceIndexEntry[]): string;
|
|
30
|
+
/** Pure renderer for the apply-instructions markdown section. */
|
|
31
|
+
export declare function renderReferencedStoresSection(entries: ReferenceIndexEntry[]): string;
|
|
32
|
+
/**
|
|
33
|
+
* Strings rendered into agent guidance can come from cloned content
|
|
34
|
+
* (spec directory names, Purpose lines, config-declared remotes). One
|
|
35
|
+
* line in, one line out: control characters and newlines must never
|
|
36
|
+
* let hostile content forge instruction lines (slice 6.1 hardening).
|
|
37
|
+
*/
|
|
38
|
+
export declare function sanitizeInline(value: string, maxLength?: number): string;
|
|
39
|
+
export interface AssembleReferenceIndexInput {
|
|
40
|
+
references: DeclarationEntry[];
|
|
41
|
+
resolvedRoot: ResolvedOpenSpecRoot;
|
|
42
|
+
globalDataDir?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Health mode (3.6): false skips the spec-file reads AND the byte
|
|
45
|
+
* budget — entries carry no `specs`/`fetch` keys at all, and the
|
|
46
|
+
* content-only truncation diagnostic can never appear.
|
|
47
|
+
*/
|
|
48
|
+
includeSpecs?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Pre-read registry entries (3.6): `[]` = registry empty or absent,
|
|
51
|
+
* `null` = unreadable, undefined = read internally as before.
|
|
52
|
+
* (Mirrors the internal post-read variable — never inject a raw
|
|
53
|
+
* read result: a healthy-absent registry reads as null.)
|
|
54
|
+
*/
|
|
55
|
+
registryEntries?: ReturnType<typeof listStoreRegistryEntries> | null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Builds the referenced-store index. One registry read per call; one
|
|
59
|
+
* level deep (a referenced store's own references are never followed);
|
|
60
|
+
* self-references omitted; every failure degrades to a warning entry.
|
|
61
|
+
*/
|
|
62
|
+
export declare function assembleReferenceIndex(input: AssembleReferenceIndexInput): Promise<ReferenceIndexEntry[]>;
|
|
63
|
+
//# sourceMappingURL=references.d.ts.map
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Referenced-store index assembly (slice 3.1).
|
|
3
|
+
*
|
|
4
|
+
* A root's `openspec/config.yaml` may declare `references:` — store ids
|
|
5
|
+
* whose specs the root's work draws on. Instructions output carries an
|
|
6
|
+
* INDEX of those stores' specs (id, one-line summary, fetch recipe via
|
|
7
|
+
* `--store`), built live from the registered checkouts at assembly time.
|
|
8
|
+
* Content is never inlined; root resolution is never affected; problems
|
|
9
|
+
* degrade to `warning` diagnostics instead of failing generation.
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'node:fs/promises';
|
|
12
|
+
import * as os from 'node:os';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import { makeStoreDiagnostic } from './store/errors.js';
|
|
15
|
+
import { isValidStoreId, listStoreRegistryEntries, readStoreRegistryState, } from './store/foundation.js';
|
|
16
|
+
import { getStoreRootForBackend } from './store/registry.js';
|
|
17
|
+
import { inspectRegisteredStore } from './root-selection.js';
|
|
18
|
+
import { getSpecIds } from '../utils/item-discovery.js';
|
|
19
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
20
|
+
import { MAX_CONTEXT_SIZE } from './project-config.js';
|
|
21
|
+
/**
|
|
22
|
+
* Shares the project-context cap: the rendered index is prompt material.
|
|
23
|
+
* Measured in UTF-8 bytes against the XML rendering (the larger of the
|
|
24
|
+
* two), entries and diagnostics included; only the truncation warning
|
|
25
|
+
* itself is exempt (no oscillation).
|
|
26
|
+
*/
|
|
27
|
+
const MAX_RENDERED_INDEX_SIZE = MAX_CONTEXT_SIZE;
|
|
28
|
+
function warning(code, message, fix) {
|
|
29
|
+
return makeStoreDiagnostic('warning', code, message, { target: 'references', fix });
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A remote is rendered into the pasteable clone command only when it is
|
|
33
|
+
* shell-inert: no whitespace, quotes, or metacharacters, and not
|
|
34
|
+
* flag-like (a config-supplied `--upload-pack=...` must never reach a
|
|
35
|
+
* command agents execute verbatim). Anything else falls back to the
|
|
36
|
+
* teammate-checkout wording.
|
|
37
|
+
*/
|
|
38
|
+
function isShellSafeRemote(remote) {
|
|
39
|
+
return /^[A-Za-z0-9@:/._~+-]+$/.test(remote) && !remote.startsWith('-');
|
|
40
|
+
}
|
|
41
|
+
function registerFix(id, remote) {
|
|
42
|
+
if (remote && isShellSafeRemote(remote)) {
|
|
43
|
+
// Verbatim-pasteable: absolute home path because tilde never
|
|
44
|
+
// expands outside a shell and agent JSON consumers execute argv.
|
|
45
|
+
// The checkout is quoted (homedirs may contain spaces); the remote
|
|
46
|
+
// is unquoted but gated by isShellSafeRemote above.
|
|
47
|
+
const checkout = path.join(os.homedir(), 'openspec', id);
|
|
48
|
+
// The fix renders on the machine that will paste it: POSIX shells
|
|
49
|
+
// get single quotes; cmd/PowerShell treat single quotes as literal
|
|
50
|
+
// characters, so win32 gets double quotes (valid everywhere).
|
|
51
|
+
const quoted = process.platform === 'win32' ? `"${checkout}"` : `'${checkout}'`;
|
|
52
|
+
return `git clone -- ${remote} ${quoted} && openspec store register ${quoted} --id ${id}`;
|
|
53
|
+
}
|
|
54
|
+
return `Get a checkout from a teammate and run: openspec store register <path> --id ${id}`;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Tolerant first-Purpose-line extraction. parseSpec() throws on specs
|
|
58
|
+
* without Purpose/Requirements sections; the index must never fail on an
|
|
59
|
+
* imperfect upstream spec, so this scans for the heading directly —
|
|
60
|
+
* fence-aware, so `## Purpose` inside a code block never matches, and
|
|
61
|
+
* tolerant of CommonMark closing hashes (`## Purpose ##`).
|
|
62
|
+
*/
|
|
63
|
+
export function extractFirstPurposeLine(markdown) {
|
|
64
|
+
const lines = markdown.split(/\r?\n/);
|
|
65
|
+
let inPurpose = false;
|
|
66
|
+
let fenceMarker = null;
|
|
67
|
+
for (const line of lines) {
|
|
68
|
+
// CommonMark: a fence closes only with its own marker kind.
|
|
69
|
+
const fence = line.match(/^\s*(```|~~~)/);
|
|
70
|
+
if (fence) {
|
|
71
|
+
if (fenceMarker === null) {
|
|
72
|
+
fenceMarker = fence[1];
|
|
73
|
+
}
|
|
74
|
+
else if (fence[1] === fenceMarker) {
|
|
75
|
+
fenceMarker = null;
|
|
76
|
+
}
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (fenceMarker !== null) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const heading = line.match(/^(#{1,6})\s+(.*)$/);
|
|
83
|
+
if (heading) {
|
|
84
|
+
if (inPurpose) {
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
const title = heading[2].replace(/\s+#+\s*$/, '').trim();
|
|
88
|
+
inPurpose = title.toLowerCase() === 'purpose';
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (inPurpose && line.trim().length > 0) {
|
|
92
|
+
return line.trim();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return '';
|
|
96
|
+
}
|
|
97
|
+
async function collectSpecEntries(referencedRoot) {
|
|
98
|
+
const specIds = await getSpecIds(referencedRoot);
|
|
99
|
+
return Promise.all(specIds.map(async (specId) => {
|
|
100
|
+
let summary = '';
|
|
101
|
+
try {
|
|
102
|
+
const content = await fs.readFile(path.join(referencedRoot, 'openspec', 'specs', specId, 'spec.md'), 'utf-8');
|
|
103
|
+
summary = sanitizeInline(extractFirstPurposeLine(content));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Unreadable spec file: index the id with an empty summary.
|
|
107
|
+
}
|
|
108
|
+
return { id: specId, summary };
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
export function fetchRecipe(storeId) {
|
|
112
|
+
return `openspec show <spec-id> --type spec --store ${storeId}`;
|
|
113
|
+
}
|
|
114
|
+
function specLine(spec) {
|
|
115
|
+
// Ids are raw directory names from cloned content; summaries are
|
|
116
|
+
// sanitized at index time (collectSpecEntries).
|
|
117
|
+
const id = sanitizeInline(spec.id, 100);
|
|
118
|
+
return spec.summary ? ` - ${id}: ${spec.summary}` : ` - ${id}`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Pure renderer for the artifact-instructions XML block. Also the byte
|
|
122
|
+
* budget's measuring stick (it is the larger rendering).
|
|
123
|
+
*/
|
|
124
|
+
export function renderReferencedStoresBlock(entries) {
|
|
125
|
+
const lines = [
|
|
126
|
+
'<referenced_stores>',
|
|
127
|
+
'<!-- Read-only upstream context. Fetch what you need; cite what you use. -->',
|
|
128
|
+
];
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
lines.push(...renderEntryLines(entry));
|
|
131
|
+
}
|
|
132
|
+
lines.push('</referenced_stores>');
|
|
133
|
+
return lines.join('\n');
|
|
134
|
+
}
|
|
135
|
+
/** Pure renderer for the apply-instructions markdown section. */
|
|
136
|
+
export function renderReferencedStoresSection(entries) {
|
|
137
|
+
const lines = [
|
|
138
|
+
'### Referenced Stores',
|
|
139
|
+
'',
|
|
140
|
+
'Read-only upstream context. Fetch what you need; cite what you use.',
|
|
141
|
+
'',
|
|
142
|
+
];
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
lines.push(...renderEntryLines(entry));
|
|
145
|
+
}
|
|
146
|
+
return lines.join('\n');
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Strings rendered into agent guidance can come from cloned content
|
|
150
|
+
* (spec directory names, Purpose lines, config-declared remotes). One
|
|
151
|
+
* line in, one line out: control characters and newlines must never
|
|
152
|
+
* let hostile content forge instruction lines (slice 6.1 hardening).
|
|
153
|
+
*/
|
|
154
|
+
export function sanitizeInline(value, maxLength = 300) {
|
|
155
|
+
// eslint-disable-next-line no-control-regex
|
|
156
|
+
const flattened = value.replace(/[\u0000-\u001f\u007f]+/g, ' ').trim();
|
|
157
|
+
return flattened.length > maxLength ? `${flattened.slice(0, maxLength)}…` : flattened;
|
|
158
|
+
}
|
|
159
|
+
function renderEntryLines(entry) {
|
|
160
|
+
const lines = [];
|
|
161
|
+
if (entry.root !== undefined) {
|
|
162
|
+
lines.push(`Store ${entry.store_id} (${entry.root}):`);
|
|
163
|
+
for (const spec of entry.specs ?? []) {
|
|
164
|
+
lines.push(specLine(spec));
|
|
165
|
+
}
|
|
166
|
+
if (entry.fetch) {
|
|
167
|
+
lines.push(` Fetch: ${entry.fetch}`);
|
|
168
|
+
}
|
|
169
|
+
// Diagnostics on a resolved entry (e.g. truncation) render message
|
|
170
|
+
// AND fix — an orphan fix line would hide that the list is partial.
|
|
171
|
+
for (const diagnostic of entry.status) {
|
|
172
|
+
lines.push(` Note: ${diagnostic.message}`);
|
|
173
|
+
if (diagnostic.fix) {
|
|
174
|
+
lines.push(` Fix: ${diagnostic.fix}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
for (const diagnostic of entry.status) {
|
|
180
|
+
lines.push(`Store ${entry.store_id}: ${diagnostic.message}`);
|
|
181
|
+
if (diagnostic.fix) {
|
|
182
|
+
lines.push(` Fix: ${diagnostic.fix}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return lines;
|
|
187
|
+
}
|
|
188
|
+
function renderedByteSize(entries) {
|
|
189
|
+
return Buffer.byteLength(renderReferencedStoresBlock(entries), 'utf-8');
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Builds the referenced-store index. One registry read per call; one
|
|
193
|
+
* level deep (a referenced store's own references are never followed);
|
|
194
|
+
* self-references omitted; every failure degrades to a warning entry.
|
|
195
|
+
*/
|
|
196
|
+
export async function assembleReferenceIndex(input) {
|
|
197
|
+
const declarations = input.references;
|
|
198
|
+
if (declarations.length === 0) {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
// null means the registry itself was unreadable (corrupt file).
|
|
202
|
+
let registryEntries;
|
|
203
|
+
if (input.registryEntries !== undefined) {
|
|
204
|
+
registryEntries = input.registryEntries;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
try {
|
|
208
|
+
const registry = await readStoreRegistryState(input.globalDataDir ? { globalDataDir: input.globalDataDir } : {});
|
|
209
|
+
registryEntries = registry ? listStoreRegistryEntries(registry) : [];
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
registryEntries = null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const includeSpecs = input.includeSpecs !== false;
|
|
216
|
+
const resolvedRootPath = FileSystemUtils.canonicalizeExistingPath(input.resolvedRoot.path);
|
|
217
|
+
const entries = [];
|
|
218
|
+
for (const { id, remote } of declarations) {
|
|
219
|
+
// Registry-independent checks come first: an invalid id is an
|
|
220
|
+
// invalid id (and a self-reference is omittable) even when the
|
|
221
|
+
// registry is corrupt. The declared remote is only consulted after
|
|
222
|
+
// the id passes grammar.
|
|
223
|
+
if (!isValidStoreId(id)) {
|
|
224
|
+
entries.push({
|
|
225
|
+
store_id: id,
|
|
226
|
+
status: [
|
|
227
|
+
warning('reference_invalid_id', `Reference '${id}' is not a valid store id.`, 'Use kebab-case store ids in the references list.'),
|
|
228
|
+
],
|
|
229
|
+
});
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (input.resolvedRoot.storeId === id) {
|
|
233
|
+
continue; // Self-reference: meaningless, silently omitted.
|
|
234
|
+
}
|
|
235
|
+
if (registryEntries === null) {
|
|
236
|
+
entries.push({
|
|
237
|
+
store_id: id,
|
|
238
|
+
status: [
|
|
239
|
+
warning('reference_registry_unreadable', `Referenced store '${id}' cannot be checked: the store registry is unreadable.`, 'Run: openspec store doctor'),
|
|
240
|
+
],
|
|
241
|
+
});
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const registryEntry = registryEntries.find((candidate) => candidate.id === id);
|
|
245
|
+
if (!registryEntry) {
|
|
246
|
+
entries.push({
|
|
247
|
+
store_id: id,
|
|
248
|
+
status: [
|
|
249
|
+
warning('reference_unresolved', `Referenced store '${id}' is not registered on this machine.`, registerFix(id, remote)),
|
|
250
|
+
],
|
|
251
|
+
});
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
let inspection;
|
|
255
|
+
try {
|
|
256
|
+
const storeRoot = getStoreRootForBackend(registryEntry.backend);
|
|
257
|
+
inspection = await inspectRegisteredStore(id, storeRoot);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
inspection = { kind: 'inspection_error', error };
|
|
261
|
+
}
|
|
262
|
+
if (inspection.kind !== 'ok') {
|
|
263
|
+
entries.push({
|
|
264
|
+
store_id: id,
|
|
265
|
+
status: [
|
|
266
|
+
warning('reference_root_unhealthy', `Referenced store '${id}' is registered but not usable (${inspection.kind.replace(/_/g, ' ')}).`, `Run: openspec store doctor ${id}`),
|
|
267
|
+
],
|
|
268
|
+
});
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (inspection.canonicalRoot === resolvedRootPath) {
|
|
272
|
+
continue; // Self-reference by path: silently omitted.
|
|
273
|
+
}
|
|
274
|
+
if (!includeSpecs) {
|
|
275
|
+
// Health mode: resolution facts only — no content, no budget.
|
|
276
|
+
entries.push({ store_id: id, root: inspection.canonicalRoot, status: [] });
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const specs = await collectSpecEntries(inspection.canonicalRoot);
|
|
280
|
+
const entry = {
|
|
281
|
+
store_id: id,
|
|
282
|
+
root: inspection.canonicalRoot,
|
|
283
|
+
specs,
|
|
284
|
+
fetch: fetchRecipe(id),
|
|
285
|
+
status: [],
|
|
286
|
+
};
|
|
287
|
+
// Budget the real rendering: keep the longest spec-list prefix whose
|
|
288
|
+
// full rendered index stays under the cap. The truncation warning
|
|
289
|
+
// itself is exempt (added after the size decision — no oscillation).
|
|
290
|
+
entries.push(entry);
|
|
291
|
+
if (renderedByteSize(entries) > MAX_RENDERED_INDEX_SIZE) {
|
|
292
|
+
let low = 0;
|
|
293
|
+
let high = specs.length;
|
|
294
|
+
while (low < high) {
|
|
295
|
+
const mid = Math.ceil((low + high) / 2);
|
|
296
|
+
entry.specs = specs.slice(0, mid);
|
|
297
|
+
if (renderedByteSize(entries) > MAX_RENDERED_INDEX_SIZE) {
|
|
298
|
+
high = mid - 1;
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
low = mid;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
entry.specs = specs.slice(0, low);
|
|
305
|
+
entry.status.push(warning('reference_index_truncated', `Referenced store '${id}' index truncated at the 50KB budget (${low} of ${specs.length} specs listed).`, `List the rest directly: openspec list --specs --store ${id}`));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return entries;
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=references.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relationship health composition (slice 3.6).
|
|
3
|
+
*
|
|
4
|
+
* One read-only answer to "are the roots this work relates to available
|
|
5
|
+
* on this machine?" — pure composition over inputs the doctor command
|
|
6
|
+
* gathers. The lock's four categories stay separated: root health,
|
|
7
|
+
* store metadata health, and reference health. Nothing here (or
|
|
8
|
+
* downstream) clones, syncs, or repairs.
|
|
9
|
+
*/
|
|
10
|
+
import { type StoreDiagnostic } from './store/errors.js';
|
|
11
|
+
import { type ReferenceIndexEntry } from './references.js';
|
|
12
|
+
import { type ResolvedOpenSpecRoot } from './root-selection.js';
|
|
13
|
+
export interface RelationshipHealth {
|
|
14
|
+
root: {
|
|
15
|
+
path: string;
|
|
16
|
+
source: ResolvedOpenSpecRoot['source'];
|
|
17
|
+
store_id?: string;
|
|
18
|
+
healthy: boolean;
|
|
19
|
+
status: StoreDiagnostic[];
|
|
20
|
+
};
|
|
21
|
+
store: {
|
|
22
|
+
id: string;
|
|
23
|
+
metadata: {
|
|
24
|
+
present: boolean;
|
|
25
|
+
valid: boolean;
|
|
26
|
+
remote?: string;
|
|
27
|
+
};
|
|
28
|
+
origin_url?: string;
|
|
29
|
+
status: StoreDiagnostic[];
|
|
30
|
+
} | null;
|
|
31
|
+
references: ReferenceIndexEntry[];
|
|
32
|
+
status: StoreDiagnostic[];
|
|
33
|
+
}
|
|
34
|
+
export interface InspectRelationshipsInput {
|
|
35
|
+
root: ResolvedOpenSpecRoot;
|
|
36
|
+
rootHealthy: boolean;
|
|
37
|
+
rootStatus?: StoreDiagnostic[];
|
|
38
|
+
/** Store facts for store-backed roots (explicit or declared). */
|
|
39
|
+
storeFacts?: {
|
|
40
|
+
id: string;
|
|
41
|
+
metadataPresent: boolean;
|
|
42
|
+
metadataValid: boolean;
|
|
43
|
+
canonicalRemote?: string;
|
|
44
|
+
originUrl?: string;
|
|
45
|
+
};
|
|
46
|
+
referenceEntries: ReferenceIndexEntry[];
|
|
47
|
+
registryUnreadable: boolean;
|
|
48
|
+
/** A real root whose config also declares a store: pointer (3.2). */
|
|
49
|
+
bothShapesPointer?: {
|
|
50
|
+
value: string;
|
|
51
|
+
filePath: string;
|
|
52
|
+
};
|
|
53
|
+
/** A real root whose store: pointer value is malformed (3.2). */
|
|
54
|
+
malformedPointer?: {
|
|
55
|
+
filePath: string;
|
|
56
|
+
reason: 'unparseable' | 'non_string';
|
|
57
|
+
};
|
|
58
|
+
/** Reference declarations in a pointer directory's own config are inert. */
|
|
59
|
+
inertPointerDeclarations?: {
|
|
60
|
+
filePath: string;
|
|
61
|
+
fields: string[];
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export declare function inspectRelationships(input: InspectRelationshipsInput): RelationshipHealth;
|
|
65
|
+
//# sourceMappingURL=relationship-health.d.ts.map
|