@codewalla_india/openspec 1.0.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/LICENSE +22 -0
- package/README.md +225 -0
- package/bin/openspec.js +5 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/index.js +548 -0
- package/dist/commands/change.d.ts +39 -0
- package/dist/commands/change.js +279 -0
- package/dist/commands/completion.d.ts +72 -0
- package/dist/commands/completion.js +264 -0
- package/dist/commands/config.d.ts +36 -0
- package/dist/commands/config.js +552 -0
- 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/feedback.d.ts +9 -0
- package/dist/commands/feedback.js +183 -0
- package/dist/commands/schema.d.ts +6 -0
- package/dist/commands/schema.js +869 -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 +19 -0
- package/dist/commands/show.js +177 -0
- package/dist/commands/spec.d.ts +19 -0
- package/dist/commands/spec.js +236 -0
- package/dist/commands/store.d.ts +3 -0
- package/dist/commands/store.js +547 -0
- package/dist/commands/validate.d.ts +26 -0
- package/dist/commands/validate.js +330 -0
- package/dist/commands/workflow/index.d.ts +17 -0
- package/dist/commands/workflow/index.js +12 -0
- package/dist/commands/workflow/instructions.d.ts +45 -0
- package/dist/commands/workflow/instructions.js +500 -0
- package/dist/commands/workflow/new-change.d.ts +20 -0
- package/dist/commands/workflow/new-change.js +106 -0
- package/dist/commands/workflow/schemas.d.ts +10 -0
- package/dist/commands/workflow/schemas.js +34 -0
- package/dist/commands/workflow/shared.d.ts +84 -0
- package/dist/commands/workflow/shared.js +133 -0
- package/dist/commands/workflow/status.d.ts +16 -0
- package/dist/commands/workflow/status.js +92 -0
- package/dist/commands/workflow/templates.d.ts +16 -0
- package/dist/commands/workflow/templates.js +69 -0
- 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 +22 -0
- package/dist/core/archive.js +471 -0
- package/dist/core/artifact-graph/graph.d.ts +56 -0
- package/dist/core/artifact-graph/graph.js +141 -0
- package/dist/core/artifact-graph/index.d.ts +9 -0
- package/dist/core/artifact-graph/index.js +14 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +188 -0
- package/dist/core/artifact-graph/instruction-loader.js +233 -0
- package/dist/core/artifact-graph/outputs.d.ts +14 -0
- package/dist/core/artifact-graph/outputs.js +39 -0
- package/dist/core/artifact-graph/resolver.d.ts +81 -0
- package/dist/core/artifact-graph/resolver.js +257 -0
- package/dist/core/artifact-graph/schema.d.ts +13 -0
- package/dist/core/artifact-graph/schema.js +108 -0
- package/dist/core/artifact-graph/state.d.ts +12 -0
- package/dist/core/artifact-graph/state.js +31 -0
- package/dist/core/artifact-graph/types.d.ts +40 -0
- package/dist/core/artifact-graph/types.js +29 -0
- package/dist/core/available-tools.d.ts +17 -0
- package/dist/core/available-tools.js +43 -0
- package/dist/core/change-metadata/index.d.ts +2 -0
- package/dist/core/change-metadata/index.js +2 -0
- package/dist/core/change-metadata/schema.d.ts +19 -0
- package/dist/core/change-metadata/schema.js +30 -0
- package/dist/core/change-status-policy.d.ts +37 -0
- package/dist/core/change-status-policy.js +35 -0
- package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
- package/dist/core/command-generation/adapters/amazon-q.js +26 -0
- package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
- package/dist/core/command-generation/adapters/antigravity.js +26 -0
- package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
- package/dist/core/command-generation/adapters/auggie.js +27 -0
- package/dist/core/command-generation/adapters/bob.d.ts +14 -0
- package/dist/core/command-generation/adapters/bob.js +32 -0
- package/dist/core/command-generation/adapters/claude.d.ts +13 -0
- package/dist/core/command-generation/adapters/claude.js +37 -0
- package/dist/core/command-generation/adapters/cline.d.ts +14 -0
- package/dist/core/command-generation/adapters/cline.js +27 -0
- package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
- package/dist/core/command-generation/adapters/codebuddy.js +28 -0
- package/dist/core/command-generation/adapters/codex.d.ts +16 -0
- package/dist/core/command-generation/adapters/codex.js +39 -0
- package/dist/core/command-generation/adapters/continue.d.ts +13 -0
- package/dist/core/command-generation/adapters/continue.js +28 -0
- package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
- package/dist/core/command-generation/adapters/costrict.js +27 -0
- package/dist/core/command-generation/adapters/crush.d.ts +13 -0
- package/dist/core/command-generation/adapters/crush.js +30 -0
- package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
- package/dist/core/command-generation/adapters/cursor.js +31 -0
- package/dist/core/command-generation/adapters/factory.d.ts +13 -0
- package/dist/core/command-generation/adapters/factory.js +27 -0
- package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
- package/dist/core/command-generation/adapters/gemini.js +26 -0
- package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
- package/dist/core/command-generation/adapters/github-copilot.js +26 -0
- package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
- package/dist/core/command-generation/adapters/iflow.js +29 -0
- package/dist/core/command-generation/adapters/index.d.ts +32 -0
- package/dist/core/command-generation/adapters/index.js +32 -0
- package/dist/core/command-generation/adapters/junie.d.ts +13 -0
- package/dist/core/command-generation/adapters/junie.js +26 -0
- package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
- package/dist/core/command-generation/adapters/kilocode.js +23 -0
- package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
- package/dist/core/command-generation/adapters/kiro.js +26 -0
- package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
- package/dist/core/command-generation/adapters/lingma.js +30 -0
- package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
- package/dist/core/command-generation/adapters/opencode.js +29 -0
- package/dist/core/command-generation/adapters/pi.d.ts +18 -0
- package/dist/core/command-generation/adapters/pi.js +42 -0
- package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
- package/dist/core/command-generation/adapters/qoder.js +30 -0
- package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
- package/dist/core/command-generation/adapters/qwen.js +26 -0
- package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
- package/dist/core/command-generation/adapters/roocode.js +27 -0
- package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
- package/dist/core/command-generation/adapters/windsurf.js +38 -0
- package/dist/core/command-generation/generator.d.ts +21 -0
- package/dist/core/command-generation/generator.js +27 -0
- package/dist/core/command-generation/index.d.ts +22 -0
- package/dist/core/command-generation/index.js +24 -0
- package/dist/core/command-generation/registry.d.ts +36 -0
- package/dist/core/command-generation/registry.js +98 -0
- package/dist/core/command-generation/types.d.ts +56 -0
- package/dist/core/command-generation/types.js +8 -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.d.ts +3 -0
- package/dist/core/completions/command-registry.js +778 -0
- package/dist/core/completions/completion-provider.d.ts +71 -0
- package/dist/core/completions/completion-provider.js +129 -0
- package/dist/core/completions/factory.d.ts +64 -0
- package/dist/core/completions/factory.js +75 -0
- package/dist/core/completions/generators/bash-generator.d.ts +35 -0
- package/dist/core/completions/generators/bash-generator.js +230 -0
- package/dist/core/completions/generators/fish-generator.d.ts +32 -0
- package/dist/core/completions/generators/fish-generator.js +160 -0
- package/dist/core/completions/generators/powershell-generator.d.ts +36 -0
- package/dist/core/completions/generators/powershell-generator.js +266 -0
- package/dist/core/completions/generators/zsh-generator.d.ts +47 -0
- package/dist/core/completions/generators/zsh-generator.js +276 -0
- package/dist/core/completions/installers/bash-installer.d.ts +87 -0
- package/dist/core/completions/installers/bash-installer.js +321 -0
- package/dist/core/completions/installers/fish-installer.d.ts +43 -0
- package/dist/core/completions/installers/fish-installer.js +151 -0
- package/dist/core/completions/installers/powershell-installer.d.ts +102 -0
- package/dist/core/completions/installers/powershell-installer.js +415 -0
- package/dist/core/completions/installers/zsh-installer.d.ts +117 -0
- package/dist/core/completions/installers/zsh-installer.js +424 -0
- package/dist/core/completions/shared-flags.d.ts +13 -0
- package/dist/core/completions/shared-flags.js +33 -0
- package/dist/core/completions/templates/bash-templates.d.ts +6 -0
- package/dist/core/completions/templates/bash-templates.js +30 -0
- package/dist/core/completions/templates/fish-templates.d.ts +7 -0
- package/dist/core/completions/templates/fish-templates.js +45 -0
- package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
- package/dist/core/completions/templates/powershell-templates.js +34 -0
- package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
- package/dist/core/completions/templates/zsh-templates.js +45 -0
- package/dist/core/completions/types.d.ts +101 -0
- package/dist/core/completions/types.js +2 -0
- package/dist/core/comprehension/config.d.ts +20 -0
- package/dist/core/comprehension/config.js +23 -0
- package/dist/core/comprehension/fingerprint.d.ts +5 -0
- package/dist/core/comprehension/fingerprint.js +25 -0
- package/dist/core/comprehension/index.d.ts +49 -0
- package/dist/core/comprehension/index.js +78 -0
- package/dist/core/comprehension/pass-record.d.ts +29 -0
- package/dist/core/comprehension/pass-record.js +64 -0
- package/dist/core/comprehension/stats.d.ts +18 -0
- package/dist/core/comprehension/stats.js +41 -0
- package/dist/core/config-prompts.d.ts +9 -0
- package/dist/core/config-prompts.js +34 -0
- package/dist/core/config-schema.d.ts +87 -0
- package/dist/core/config-schema.js +239 -0
- package/dist/core/config.d.ts +18 -0
- package/dist/core/config.js +39 -0
- package/dist/core/converters/json-converter.d.ts +6 -0
- package/dist/core/converters/json-converter.js +51 -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 +51 -0
- package/dist/core/global-config.js +124 -0
- package/dist/core/id.d.ts +17 -0
- package/dist/core/id.js +30 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.js +7 -0
- package/dist/core/init.d.ts +37 -0
- package/dist/core/init.js +613 -0
- package/dist/core/legacy-cleanup.d.ts +162 -0
- package/dist/core/legacy-cleanup.js +514 -0
- package/dist/core/list.d.ts +11 -0
- package/dist/core/list.js +185 -0
- package/dist/core/migration.d.ts +23 -0
- package/dist/core/migration.js +108 -0
- 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 +192 -0
- package/dist/core/parsers/change-parser.d.ts +13 -0
- package/dist/core/parsers/change-parser.js +197 -0
- package/dist/core/parsers/markdown-parser.d.ts +26 -0
- package/dist/core/parsers/markdown-parser.js +227 -0
- package/dist/core/parsers/requirement-blocks.d.ts +37 -0
- package/dist/core/parsers/requirement-blocks.js +201 -0
- package/dist/core/parsers/spec-structure.d.ts +9 -0
- package/dist/core/parsers/spec-structure.js +88 -0
- package/dist/core/planning-home.d.ts +16 -0
- package/dist/core/planning-home.js +67 -0
- package/dist/core/profile-sync-drift.d.ts +38 -0
- package/dist/core/profile-sync-drift.js +200 -0
- package/dist/core/profiles.d.ts +26 -0
- package/dist/core/profiles.js +40 -0
- package/dist/core/project-config.d.ts +120 -0
- package/dist/core/project-config.js +406 -0
- 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.d.ts +13 -0
- package/dist/core/schemas/base.schema.js +13 -0
- package/dist/core/schemas/change.schema.d.ts +73 -0
- package/dist/core/schemas/change.schema.js +31 -0
- package/dist/core/schemas/index.d.ts +4 -0
- package/dist/core/schemas/index.js +4 -0
- package/dist/core/schemas/spec.schema.d.ts +18 -0
- package/dist/core/schemas/spec.schema.js +15 -0
- package/dist/core/shared/index.d.ts +8 -0
- package/dist/core/shared/index.js +8 -0
- package/dist/core/shared/skill-generation.d.ts +49 -0
- package/dist/core/shared/skill-generation.js +96 -0
- package/dist/core/shared/tool-detection.d.ts +71 -0
- package/dist/core/shared/tool-detection.js +158 -0
- package/dist/core/specs-apply.d.ts +78 -0
- package/dist/core/specs-apply.js +394 -0
- package/dist/core/store/errors.d.ts +20 -0
- package/dist/core/store/errors.js +22 -0
- 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/store/index.d.ts +5 -0
- package/dist/core/store/index.js +5 -0
- package/dist/core/store/operations.d.ts +114 -0
- package/dist/core/store/operations.js +783 -0
- package/dist/core/store/registry.d.ts +58 -0
- package/dist/core/store/registry.js +275 -0
- package/dist/core/styles/palette.d.ts +7 -0
- package/dist/core/styles/palette.js +8 -0
- package/dist/core/templates/index.d.ts +8 -0
- package/dist/core/templates/index.js +9 -0
- package/dist/core/templates/skill-templates.d.ts +19 -0
- package/dist/core/templates/skill-templates.js +18 -0
- package/dist/core/templates/types.d.ts +19 -0
- package/dist/core/templates/types.js +5 -0
- package/dist/core/templates/workflows/apply-change.d.ts +10 -0
- package/dist/core/templates/workflows/apply-change.js +337 -0
- package/dist/core/templates/workflows/archive-change.d.ts +10 -0
- package/dist/core/templates/workflows/archive-change.js +278 -0
- package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
- package/dist/core/templates/workflows/bulk-archive-change.js +493 -0
- package/dist/core/templates/workflows/comprehension-guidance.d.ts +9 -0
- package/dist/core/templates/workflows/comprehension-guidance.js +58 -0
- package/dist/core/templates/workflows/continue-change.d.ts +10 -0
- package/dist/core/templates/workflows/continue-change.js +239 -0
- package/dist/core/templates/workflows/explore.d.ts +10 -0
- package/dist/core/templates/workflows/explore.js +464 -0
- package/dist/core/templates/workflows/feedback.d.ts +9 -0
- package/dist/core/templates/workflows/feedback.js +108 -0
- package/dist/core/templates/workflows/ff-change.d.ts +10 -0
- package/dist/core/templates/workflows/ff-change.js +205 -0
- package/dist/core/templates/workflows/mcp-guidance.d.ts +13 -0
- package/dist/core/templates/workflows/mcp-guidance.js +116 -0
- package/dist/core/templates/workflows/new-change.d.ts +10 -0
- package/dist/core/templates/workflows/new-change.js +148 -0
- package/dist/core/templates/workflows/onboard.d.ts +10 -0
- package/dist/core/templates/workflows/onboard.js +566 -0
- package/dist/core/templates/workflows/propose.d.ts +10 -0
- package/dist/core/templates/workflows/propose.js +228 -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.d.ts +10 -0
- package/dist/core/templates/workflows/sync-specs.js +291 -0
- package/dist/core/templates/workflows/verify-change.d.ts +10 -0
- package/dist/core/templates/workflows/verify-change.js +346 -0
- package/dist/core/update.d.ts +82 -0
- package/dist/core/update.js +557 -0
- package/dist/core/validation/constants.d.ts +34 -0
- package/dist/core/validation/constants.js +40 -0
- package/dist/core/validation/types.d.ts +18 -0
- package/dist/core/validation/types.js +2 -0
- package/dist/core/validation/validator.d.ts +44 -0
- package/dist/core/validation/validator.js +435 -0
- package/dist/core/view.d.ts +8 -0
- package/dist/core/view.js +168 -0
- 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/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/prompts/searchable-multi-select.d.ts +28 -0
- package/dist/prompts/searchable-multi-select.js +159 -0
- package/dist/telemetry/config.d.ts +38 -0
- package/dist/telemetry/config.js +136 -0
- package/dist/telemetry/index.d.ts +31 -0
- package/dist/telemetry/index.js +164 -0
- package/dist/ui/ascii-patterns.d.ts +16 -0
- package/dist/ui/ascii-patterns.js +133 -0
- package/dist/ui/welcome-screen.d.ts +10 -0
- package/dist/ui/welcome-screen.js +146 -0
- package/dist/utils/change-metadata.d.ts +55 -0
- package/dist/utils/change-metadata.js +141 -0
- package/dist/utils/change-utils.d.ts +71 -0
- package/dist/utils/change-utils.js +138 -0
- package/dist/utils/command-references.d.ts +18 -0
- package/dist/utils/command-references.js +20 -0
- package/dist/utils/file-system.d.ts +41 -0
- package/dist/utils/file-system.js +320 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/interactive.d.ts +18 -0
- package/dist/utils/interactive.js +21 -0
- package/dist/utils/item-discovery.d.ts +4 -0
- package/dist/utils/item-discovery.js +72 -0
- package/dist/utils/match.d.ts +3 -0
- package/dist/utils/match.js +22 -0
- package/dist/utils/shell-detection.d.ts +20 -0
- package/dist/utils/shell-detection.js +41 -0
- package/dist/utils/task-progress.d.ts +8 -0
- package/dist/utils/task-progress.js +36 -0
- package/package.json +84 -0
- package/schemas/spec-driven/schema.yaml +153 -0
- package/schemas/spec-driven/templates/design.md +19 -0
- package/schemas/spec-driven/templates/proposal.md +23 -0
- package/schemas/spec-driven/templates/spec.md +8 -0
- package/schemas/spec-driven/templates/tasks.md +9 -0
- package/scripts/postinstall.js +83 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progress.js';
|
|
4
|
+
import { Validator } from './validation/validator.js';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { emitStoreRootBanner, isRootSelectionError, resolveOpenSpecRoot, toRootOutput, withStoreFlag, isStoreSelectedRoot, } from './root-selection.js';
|
|
7
|
+
import { findSpecUpdates, buildUpdatedSpec, writeUpdatedSpec, } from './specs-apply.js';
|
|
8
|
+
async function listActiveChangeNames(changesDir) {
|
|
9
|
+
try {
|
|
10
|
+
const entries = await fs.readdir(changesDir, { withFileTypes: true });
|
|
11
|
+
return entries
|
|
12
|
+
.filter((entry) => entry.isDirectory() && entry.name !== 'archive')
|
|
13
|
+
.map((entry) => entry.name)
|
|
14
|
+
.sort();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* JSON mode is non-interactive: any point where the human flow would prompt or
|
|
22
|
+
* print prose instead throws this error, which becomes a machine-readable
|
|
23
|
+
* status entry with a non-zero exit code.
|
|
24
|
+
*/
|
|
25
|
+
class ArchiveBlockedError extends Error {
|
|
26
|
+
diagnostic;
|
|
27
|
+
constructor(code, message, fix) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = 'ArchiveBlockedError';
|
|
30
|
+
this.diagnostic = {
|
|
31
|
+
severity: 'error',
|
|
32
|
+
code,
|
|
33
|
+
message,
|
|
34
|
+
...(fix ? { fix } : {}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function toArchiveDiagnostic(error) {
|
|
39
|
+
if (error instanceof ArchiveBlockedError) {
|
|
40
|
+
return error.diagnostic;
|
|
41
|
+
}
|
|
42
|
+
if (isRootSelectionError(error)) {
|
|
43
|
+
return error.diagnostic;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
severity: 'error',
|
|
47
|
+
code: 'archive_error',
|
|
48
|
+
message: error instanceof Error ? error.message : String(error),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Recursively copy a directory. Used when fs.rename fails (e.g. EPERM on Windows).
|
|
53
|
+
*/
|
|
54
|
+
async function copyDirRecursive(src, dest) {
|
|
55
|
+
await fs.mkdir(dest, { recursive: true });
|
|
56
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
const srcPath = path.join(src, entry.name);
|
|
59
|
+
const destPath = path.join(dest, entry.name);
|
|
60
|
+
if (entry.isDirectory()) {
|
|
61
|
+
await copyDirRecursive(srcPath, destPath);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
await fs.copyFile(srcPath, destPath);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Move a directory from src to dest. On Windows, fs.rename() often fails with
|
|
70
|
+
* EPERM when the directory is non-empty or another process has it open (IDE,
|
|
71
|
+
* file watcher, antivirus). Fall back to copy-then-remove when rename fails
|
|
72
|
+
* with EPERM or EXDEV.
|
|
73
|
+
*/
|
|
74
|
+
async function moveDirectory(src, dest) {
|
|
75
|
+
try {
|
|
76
|
+
await fs.rename(src, dest);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const code = err?.code;
|
|
80
|
+
if (code === 'EPERM' || code === 'EXDEV') {
|
|
81
|
+
await copyDirRecursive(src, dest);
|
|
82
|
+
await fs.rm(src, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export class ArchiveCommand {
|
|
90
|
+
async execute(changeName, options = {}) {
|
|
91
|
+
const json = !!options.json;
|
|
92
|
+
let root;
|
|
93
|
+
try {
|
|
94
|
+
root = await resolveOpenSpecRoot({
|
|
95
|
+
...(options.store !== undefined ? { store: options.store } : {}),
|
|
96
|
+
...(options.storePath !== undefined ? { storePath: options.storePath } : {}),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (json && isRootSelectionError(error)) {
|
|
101
|
+
this.printJsonFailure(undefined, toArchiveDiagnostic(error));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
if (json) {
|
|
107
|
+
try {
|
|
108
|
+
const result = await this.run(changeName, options, root, true);
|
|
109
|
+
if (!result) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
console.log(JSON.stringify({ archive: result, root: toRootOutput(root) }, null, 2));
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
this.printJsonFailure(root, toArchiveDiagnostic(error));
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
emitStoreRootBanner(root);
|
|
120
|
+
await this.run(changeName, options, root, false);
|
|
121
|
+
}
|
|
122
|
+
printJsonFailure(root, diagnostic) {
|
|
123
|
+
console.log(JSON.stringify({
|
|
124
|
+
archive: null,
|
|
125
|
+
...(root ? { root: toRootOutput(root) } : {}),
|
|
126
|
+
status: [diagnostic],
|
|
127
|
+
}, null, 2));
|
|
128
|
+
process.exitCode = 1;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Shared archive flow. In human mode (json=false) prompts and prose match
|
|
132
|
+
* the historical behavior and cancellations return null. In JSON mode no
|
|
133
|
+
* prose reaches stdout and every blocked path throws.
|
|
134
|
+
*/
|
|
135
|
+
async run(changeName, options, root, json) {
|
|
136
|
+
const changesDir = root.changesDir;
|
|
137
|
+
const archiveDir = root.archiveDir;
|
|
138
|
+
const mainSpecsDir = root.specsDir;
|
|
139
|
+
// Check if changes directory exists
|
|
140
|
+
try {
|
|
141
|
+
await fs.access(changesDir);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
throw new Error("No OpenSpec changes directory found. Run 'openspec init' first.");
|
|
145
|
+
}
|
|
146
|
+
// Get change name interactively if not provided
|
|
147
|
+
if (!changeName) {
|
|
148
|
+
if (json) {
|
|
149
|
+
throw new ArchiveBlockedError('archive_change_name_required', 'A change name is required: archive --json is non-interactive.', withStoreFlag(root, 'openspec archive <change-name> --json'));
|
|
150
|
+
}
|
|
151
|
+
const selectedChange = await this.selectChange(changesDir);
|
|
152
|
+
if (!selectedChange) {
|
|
153
|
+
console.log('No change selected. Aborting.');
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
changeName = selectedChange;
|
|
157
|
+
}
|
|
158
|
+
const changeDir = path.join(changesDir, changeName);
|
|
159
|
+
// Verify change exists
|
|
160
|
+
try {
|
|
161
|
+
const stat = await fs.stat(changeDir);
|
|
162
|
+
if (!stat.isDirectory()) {
|
|
163
|
+
throw new Error(`Change '${changeName}' not found.`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
const available = await listActiveChangeNames(changesDir);
|
|
168
|
+
throw new ArchiveBlockedError('archive_change_not_found', available.length > 0
|
|
169
|
+
? `Change '${changeName}' not found. Available changes: ${available.join(', ')}`
|
|
170
|
+
: `Change '${changeName}' not found. No active changes exist in this root.`);
|
|
171
|
+
}
|
|
172
|
+
const skipValidation = options.validate === false || options.noValidate === true;
|
|
173
|
+
// Validate specs and change before archiving
|
|
174
|
+
if (!skipValidation) {
|
|
175
|
+
const validator = new Validator();
|
|
176
|
+
let hasValidationErrors = false;
|
|
177
|
+
// Validate proposal.md (informative only; human mode prints warnings)
|
|
178
|
+
if (!json) {
|
|
179
|
+
const changeFile = path.join(changeDir, 'proposal.md');
|
|
180
|
+
try {
|
|
181
|
+
await fs.access(changeFile);
|
|
182
|
+
const changeReport = await validator.validateChange(changeFile);
|
|
183
|
+
// Proposal validation is informative only (do not block archive)
|
|
184
|
+
if (!changeReport.valid) {
|
|
185
|
+
console.log(chalk.yellow(`\nProposal warnings in proposal.md (non-blocking):`));
|
|
186
|
+
for (const issue of changeReport.issues) {
|
|
187
|
+
const symbol = issue.level === 'ERROR' ? '⚠' : (issue.level === 'WARNING' ? '⚠' : 'ℹ');
|
|
188
|
+
console.log(chalk.yellow(` ${symbol} ${issue.message}`));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Change file doesn't exist, skip validation
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Validate delta-formatted spec files under the change directory if present
|
|
197
|
+
const changeSpecsDir = path.join(changeDir, 'specs');
|
|
198
|
+
let hasDeltaSpecs = false;
|
|
199
|
+
try {
|
|
200
|
+
const candidates = await fs.readdir(changeSpecsDir, { withFileTypes: true });
|
|
201
|
+
for (const c of candidates) {
|
|
202
|
+
if (c.isDirectory()) {
|
|
203
|
+
try {
|
|
204
|
+
const candidatePath = path.join(changeSpecsDir, c.name, 'spec.md');
|
|
205
|
+
await fs.access(candidatePath);
|
|
206
|
+
const content = await fs.readFile(candidatePath, 'utf-8');
|
|
207
|
+
if (/^##\s+(ADDED|MODIFIED|REMOVED|RENAMED)\s+Requirements/m.test(content)) {
|
|
208
|
+
hasDeltaSpecs = true;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch { }
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch { }
|
|
217
|
+
if (hasDeltaSpecs) {
|
|
218
|
+
const deltaReport = await validator.validateChangeDeltaSpecs(changeDir);
|
|
219
|
+
if (!deltaReport.valid) {
|
|
220
|
+
hasValidationErrors = true;
|
|
221
|
+
if (!json) {
|
|
222
|
+
console.log(chalk.red(`\nValidation errors in change delta specs:`));
|
|
223
|
+
for (const issue of deltaReport.issues) {
|
|
224
|
+
if (issue.level === 'ERROR') {
|
|
225
|
+
console.log(chalk.red(` ✗ ${issue.message}`));
|
|
226
|
+
}
|
|
227
|
+
else if (issue.level === 'WARNING') {
|
|
228
|
+
console.log(chalk.yellow(` ⚠ ${issue.message}`));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (hasValidationErrors) {
|
|
235
|
+
if (json) {
|
|
236
|
+
throw new ArchiveBlockedError('archive_validation_failed', `Validation failed for change '${changeName}'.`, `Run ${withStoreFlag(root, `openspec validate ${changeName}`)} for details, fix the errors, or rerun with --no-validate.`);
|
|
237
|
+
}
|
|
238
|
+
console.log(chalk.red('\nValidation failed. Please fix the errors before archiving.'));
|
|
239
|
+
console.log(chalk.yellow('To skip validation (not recommended), use --no-validate flag.'));
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else if (json) {
|
|
244
|
+
if (!options.yes) {
|
|
245
|
+
throw new ArchiveBlockedError('archive_confirmation_required', 'Skipping validation requires confirmation: rerun with --yes.', withStoreFlag(root, 'openspec archive <change-name> --json --no-validate --yes'));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
// Log warning when validation is skipped
|
|
250
|
+
const timestamp = new Date().toISOString();
|
|
251
|
+
if (!options.yes) {
|
|
252
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
253
|
+
const proceed = await confirm({
|
|
254
|
+
message: chalk.yellow('⚠️ WARNING: Skipping validation may archive invalid specs. Continue? (y/N)'),
|
|
255
|
+
default: false
|
|
256
|
+
});
|
|
257
|
+
if (!proceed) {
|
|
258
|
+
console.log('Archive cancelled.');
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
console.log(chalk.yellow(`\n⚠️ WARNING: Skipping validation may archive invalid specs.`));
|
|
264
|
+
}
|
|
265
|
+
console.log(chalk.yellow(`[${timestamp}] Validation skipped for change: ${changeName}`));
|
|
266
|
+
console.log(chalk.yellow(`Affected files: ${changeDir}`));
|
|
267
|
+
}
|
|
268
|
+
// Show progress and check for incomplete tasks
|
|
269
|
+
const progress = await getTaskProgressForChange(changesDir, changeName);
|
|
270
|
+
if (!json) {
|
|
271
|
+
const status = formatTaskStatus(progress);
|
|
272
|
+
console.log(`Task status: ${status}`);
|
|
273
|
+
}
|
|
274
|
+
const incompleteTasks = Math.max(progress.total - progress.completed, 0);
|
|
275
|
+
if (incompleteTasks > 0) {
|
|
276
|
+
if (json) {
|
|
277
|
+
if (!options.yes) {
|
|
278
|
+
throw new ArchiveBlockedError('archive_tasks_incomplete', `${incompleteTasks} incomplete task(s) found for change '${changeName}'.`, 'Complete the tasks or rerun with --yes.');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
else if (!options.yes) {
|
|
282
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
283
|
+
const proceed = await confirm({
|
|
284
|
+
message: `Warning: ${incompleteTasks} incomplete task(s) found. Continue?`,
|
|
285
|
+
default: false
|
|
286
|
+
});
|
|
287
|
+
if (!proceed) {
|
|
288
|
+
console.log('Archive cancelled.');
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
console.log(`Warning: ${incompleteTasks} incomplete task(s) found. Continuing due to --yes flag.`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// Handle spec updates unless skipSpecs flag is set
|
|
297
|
+
let specsUpdated = false;
|
|
298
|
+
let totals;
|
|
299
|
+
if (options.skipSpecs) {
|
|
300
|
+
if (!json) {
|
|
301
|
+
console.log('Skipping spec updates (--skip-specs flag provided).');
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
// Find specs to update
|
|
306
|
+
const specUpdates = await findSpecUpdates(changeDir, mainSpecsDir);
|
|
307
|
+
if (specUpdates.length > 0) {
|
|
308
|
+
if (!json) {
|
|
309
|
+
console.log('\nSpecs to update:');
|
|
310
|
+
for (const update of specUpdates) {
|
|
311
|
+
const status = update.exists ? 'update' : 'create';
|
|
312
|
+
const capability = path.basename(path.dirname(update.target));
|
|
313
|
+
console.log(` ${capability}: ${status}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
let shouldUpdateSpecs = true;
|
|
317
|
+
if (!options.yes) {
|
|
318
|
+
if (json) {
|
|
319
|
+
throw new ArchiveBlockedError('archive_confirmation_required', `Updating ${specUpdates.length} spec(s) requires confirmation: rerun with --yes.`, withStoreFlag(root, 'openspec archive <change-name> --json --yes'));
|
|
320
|
+
}
|
|
321
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
322
|
+
shouldUpdateSpecs = await confirm({
|
|
323
|
+
message: 'Proceed with spec updates?',
|
|
324
|
+
default: true
|
|
325
|
+
});
|
|
326
|
+
if (!shouldUpdateSpecs) {
|
|
327
|
+
console.log('Skipping spec updates. Proceeding with archive.');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (shouldUpdateSpecs) {
|
|
331
|
+
// Prepare all updates first (validation pass, no writes)
|
|
332
|
+
const prepared = [];
|
|
333
|
+
try {
|
|
334
|
+
for (const update of specUpdates) {
|
|
335
|
+
const built = await buildUpdatedSpec(update, changeName, { silent: json });
|
|
336
|
+
prepared.push({ update, rebuilt: built.rebuilt, counts: built.counts });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
if (json) {
|
|
341
|
+
throw new ArchiveBlockedError('archive_spec_update_failed', String(err.message || err), 'Fix the change delta specs and rerun. No files were changed.');
|
|
342
|
+
}
|
|
343
|
+
console.log(String(err.message || err));
|
|
344
|
+
console.log('Aborted. No files were changed.');
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
// Validate every rebuilt spec before writing any of them, so a
|
|
348
|
+
// late validation failure really does leave all targets unchanged.
|
|
349
|
+
if (!skipValidation) {
|
|
350
|
+
for (const p of prepared) {
|
|
351
|
+
const specName = path.basename(path.dirname(p.update.target));
|
|
352
|
+
const report = await new Validator().validateSpecContent(specName, p.rebuilt);
|
|
353
|
+
if (!report.valid) {
|
|
354
|
+
if (json) {
|
|
355
|
+
throw new ArchiveBlockedError('archive_spec_validation_failed', `Rebuilt spec for '${specName}' failed validation. No files were changed.`, `Run ${withStoreFlag(root, `openspec validate ${specName}`)} after fixing the change deltas.`);
|
|
356
|
+
}
|
|
357
|
+
console.log(chalk.red(`\nValidation errors in rebuilt spec for ${specName} (will not write changes):`));
|
|
358
|
+
for (const issue of report.issues) {
|
|
359
|
+
if (issue.level === 'ERROR')
|
|
360
|
+
console.log(chalk.red(` ✗ ${issue.message}`));
|
|
361
|
+
else if (issue.level === 'WARNING')
|
|
362
|
+
console.log(chalk.yellow(` ⚠ ${issue.message}`));
|
|
363
|
+
}
|
|
364
|
+
console.log('Aborted. No files were changed.');
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// All validations passed; write files and display counts
|
|
370
|
+
const writeTotals = { added: 0, modified: 0, removed: 0, renamed: 0 };
|
|
371
|
+
for (const p of prepared) {
|
|
372
|
+
await writeUpdatedSpec(p.update, p.rebuilt, p.counts, {
|
|
373
|
+
silent: json,
|
|
374
|
+
// Cross-root paths must be absolute when a store is selected.
|
|
375
|
+
...(isStoreSelectedRoot(root) ? { displayPath: p.update.target } : {}),
|
|
376
|
+
});
|
|
377
|
+
writeTotals.added += p.counts.added;
|
|
378
|
+
writeTotals.modified += p.counts.modified;
|
|
379
|
+
writeTotals.removed += p.counts.removed;
|
|
380
|
+
writeTotals.renamed += p.counts.renamed;
|
|
381
|
+
}
|
|
382
|
+
specsUpdated = true;
|
|
383
|
+
totals = writeTotals;
|
|
384
|
+
if (!json) {
|
|
385
|
+
console.log(`Totals: + ${writeTotals.added}, ~ ${writeTotals.modified}, - ${writeTotals.removed}, → ${writeTotals.renamed}`);
|
|
386
|
+
console.log('Specs updated successfully.');
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Create archive directory with date prefix
|
|
392
|
+
const archiveName = `${this.getArchiveDate()}-${changeName}`;
|
|
393
|
+
const archivePath = path.join(archiveDir, archiveName);
|
|
394
|
+
// Check if archive already exists
|
|
395
|
+
let archiveExists = false;
|
|
396
|
+
try {
|
|
397
|
+
await fs.access(archivePath);
|
|
398
|
+
archiveExists = true;
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
if (error.code !== 'ENOENT') {
|
|
402
|
+
throw error;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (archiveExists) {
|
|
406
|
+
throw new ArchiveBlockedError('archive_target_exists', `Archive '${archiveName}' already exists.`);
|
|
407
|
+
}
|
|
408
|
+
// Create archive directory if needed
|
|
409
|
+
await fs.mkdir(archiveDir, { recursive: true });
|
|
410
|
+
// Move change to archive (uses copy+remove on EPERM/EXDEV, e.g. Windows)
|
|
411
|
+
await moveDirectory(changeDir, archivePath);
|
|
412
|
+
if (!json) {
|
|
413
|
+
console.log(`Change '${changeName}' archived as '${archiveName}'.`);
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
change: changeName,
|
|
417
|
+
archivedAs: archiveName,
|
|
418
|
+
path: archivePath,
|
|
419
|
+
specsUpdated,
|
|
420
|
+
...(totals ? { totals } : {}),
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
async selectChange(changesDir) {
|
|
424
|
+
const { select } = await import('@inquirer/prompts');
|
|
425
|
+
// Get all directories in changes (excluding archive)
|
|
426
|
+
const entries = await fs.readdir(changesDir, { withFileTypes: true });
|
|
427
|
+
const changeDirs = entries
|
|
428
|
+
.filter(entry => entry.isDirectory() && entry.name !== 'archive')
|
|
429
|
+
.map(entry => entry.name)
|
|
430
|
+
.sort();
|
|
431
|
+
if (changeDirs.length === 0) {
|
|
432
|
+
console.log('No active changes found.');
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
// Build choices with progress inline to avoid duplicate lists
|
|
436
|
+
let choices = changeDirs.map(name => ({ name, value: name }));
|
|
437
|
+
try {
|
|
438
|
+
const progressList = [];
|
|
439
|
+
for (const id of changeDirs) {
|
|
440
|
+
const progress = await getTaskProgressForChange(changesDir, id);
|
|
441
|
+
const status = formatTaskStatus(progress);
|
|
442
|
+
progressList.push({ id, status });
|
|
443
|
+
}
|
|
444
|
+
const nameWidth = Math.max(...progressList.map(p => p.id.length));
|
|
445
|
+
choices = progressList.map(p => ({
|
|
446
|
+
name: `${p.id.padEnd(nameWidth)} ${p.status}`,
|
|
447
|
+
value: p.id
|
|
448
|
+
}));
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
// If anything fails, fall back to simple names
|
|
452
|
+
choices = changeDirs.map(name => ({ name, value: name }));
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
const answer = await select({
|
|
456
|
+
message: 'Select a change to archive',
|
|
457
|
+
choices
|
|
458
|
+
});
|
|
459
|
+
return answer;
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
// User cancelled (Ctrl+C)
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
getArchiveDate() {
|
|
467
|
+
// Returns date in YYYY-MM-DD format
|
|
468
|
+
return new Date().toISOString().split('T')[0];
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
//# sourceMappingURL=archive.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Artifact, SchemaYaml, CompletedSet, BlockedArtifacts } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Represents an artifact dependency graph.
|
|
4
|
+
* Provides methods for querying build order, ready artifacts, and completion status.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ArtifactGraph {
|
|
7
|
+
private artifacts;
|
|
8
|
+
private schema;
|
|
9
|
+
private constructor();
|
|
10
|
+
/**
|
|
11
|
+
* Creates an ArtifactGraph from a YAML file path.
|
|
12
|
+
*/
|
|
13
|
+
static fromYaml(filePath: string): ArtifactGraph;
|
|
14
|
+
/**
|
|
15
|
+
* Creates an ArtifactGraph from YAML content string.
|
|
16
|
+
*/
|
|
17
|
+
static fromYamlContent(yamlContent: string): ArtifactGraph;
|
|
18
|
+
/**
|
|
19
|
+
* Creates an ArtifactGraph from a pre-validated schema object.
|
|
20
|
+
*/
|
|
21
|
+
static fromSchema(schema: SchemaYaml): ArtifactGraph;
|
|
22
|
+
/**
|
|
23
|
+
* Gets a single artifact by ID.
|
|
24
|
+
*/
|
|
25
|
+
getArtifact(id: string): Artifact | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Gets all artifacts in the graph.
|
|
28
|
+
*/
|
|
29
|
+
getAllArtifacts(): Artifact[];
|
|
30
|
+
/**
|
|
31
|
+
* Gets the schema name.
|
|
32
|
+
*/
|
|
33
|
+
getName(): string;
|
|
34
|
+
/**
|
|
35
|
+
* Gets the schema version.
|
|
36
|
+
*/
|
|
37
|
+
getVersion(): number;
|
|
38
|
+
/**
|
|
39
|
+
* Computes the topological build order using Kahn's algorithm.
|
|
40
|
+
* Returns artifact IDs in the order they should be built.
|
|
41
|
+
*/
|
|
42
|
+
getBuildOrder(): string[];
|
|
43
|
+
/**
|
|
44
|
+
* Gets artifacts that are ready to be created (all dependencies completed).
|
|
45
|
+
*/
|
|
46
|
+
getNextArtifacts(completed: CompletedSet): string[];
|
|
47
|
+
/**
|
|
48
|
+
* Checks if all artifacts in the graph are completed.
|
|
49
|
+
*/
|
|
50
|
+
isComplete(completed: CompletedSet): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Gets blocked artifacts and their unmet dependencies.
|
|
53
|
+
*/
|
|
54
|
+
getBlocked(completed: CompletedSet): BlockedArtifacts;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=graph.d.ts.map
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { loadSchema, parseSchema } from './schema.js';
|
|
2
|
+
/**
|
|
3
|
+
* Represents an artifact dependency graph.
|
|
4
|
+
* Provides methods for querying build order, ready artifacts, and completion status.
|
|
5
|
+
*/
|
|
6
|
+
export class ArtifactGraph {
|
|
7
|
+
artifacts;
|
|
8
|
+
schema;
|
|
9
|
+
constructor(schema) {
|
|
10
|
+
this.schema = schema;
|
|
11
|
+
this.artifacts = new Map(schema.artifacts.map(a => [a.id, a]));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Creates an ArtifactGraph from a YAML file path.
|
|
15
|
+
*/
|
|
16
|
+
static fromYaml(filePath) {
|
|
17
|
+
const schema = loadSchema(filePath);
|
|
18
|
+
return new ArtifactGraph(schema);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Creates an ArtifactGraph from YAML content string.
|
|
22
|
+
*/
|
|
23
|
+
static fromYamlContent(yamlContent) {
|
|
24
|
+
const schema = parseSchema(yamlContent);
|
|
25
|
+
return new ArtifactGraph(schema);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Creates an ArtifactGraph from a pre-validated schema object.
|
|
29
|
+
*/
|
|
30
|
+
static fromSchema(schema) {
|
|
31
|
+
return new ArtifactGraph(schema);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Gets a single artifact by ID.
|
|
35
|
+
*/
|
|
36
|
+
getArtifact(id) {
|
|
37
|
+
return this.artifacts.get(id);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Gets all artifacts in the graph.
|
|
41
|
+
*/
|
|
42
|
+
getAllArtifacts() {
|
|
43
|
+
return Array.from(this.artifacts.values());
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Gets the schema name.
|
|
47
|
+
*/
|
|
48
|
+
getName() {
|
|
49
|
+
return this.schema.name;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Gets the schema version.
|
|
53
|
+
*/
|
|
54
|
+
getVersion() {
|
|
55
|
+
return this.schema.version;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Computes the topological build order using Kahn's algorithm.
|
|
59
|
+
* Returns artifact IDs in the order they should be built.
|
|
60
|
+
*/
|
|
61
|
+
getBuildOrder() {
|
|
62
|
+
const inDegree = new Map();
|
|
63
|
+
const dependents = new Map();
|
|
64
|
+
// Initialize all artifacts
|
|
65
|
+
for (const artifact of this.artifacts.values()) {
|
|
66
|
+
inDegree.set(artifact.id, artifact.requires.length);
|
|
67
|
+
dependents.set(artifact.id, []);
|
|
68
|
+
}
|
|
69
|
+
// Build reverse adjacency (who depends on whom)
|
|
70
|
+
for (const artifact of this.artifacts.values()) {
|
|
71
|
+
for (const req of artifact.requires) {
|
|
72
|
+
dependents.get(req).push(artifact.id);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Start with roots (in-degree 0), sorted for determinism
|
|
76
|
+
const queue = [...this.artifacts.keys()]
|
|
77
|
+
.filter(id => inDegree.get(id) === 0)
|
|
78
|
+
.sort();
|
|
79
|
+
const result = [];
|
|
80
|
+
while (queue.length > 0) {
|
|
81
|
+
const current = queue.shift();
|
|
82
|
+
result.push(current);
|
|
83
|
+
// Collect newly ready artifacts, then sort before adding
|
|
84
|
+
const newlyReady = [];
|
|
85
|
+
for (const dep of dependents.get(current)) {
|
|
86
|
+
const newDegree = inDegree.get(dep) - 1;
|
|
87
|
+
inDegree.set(dep, newDegree);
|
|
88
|
+
if (newDegree === 0) {
|
|
89
|
+
newlyReady.push(dep);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
queue.push(...newlyReady.sort());
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Gets artifacts that are ready to be created (all dependencies completed).
|
|
98
|
+
*/
|
|
99
|
+
getNextArtifacts(completed) {
|
|
100
|
+
const ready = [];
|
|
101
|
+
for (const artifact of this.artifacts.values()) {
|
|
102
|
+
if (completed.has(artifact.id)) {
|
|
103
|
+
continue; // Already completed
|
|
104
|
+
}
|
|
105
|
+
const allDepsCompleted = artifact.requires.every(req => completed.has(req));
|
|
106
|
+
if (allDepsCompleted) {
|
|
107
|
+
ready.push(artifact.id);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Sort for deterministic ordering
|
|
111
|
+
return ready.sort();
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Checks if all artifacts in the graph are completed.
|
|
115
|
+
*/
|
|
116
|
+
isComplete(completed) {
|
|
117
|
+
for (const artifact of this.artifacts.values()) {
|
|
118
|
+
if (!completed.has(artifact.id)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Gets blocked artifacts and their unmet dependencies.
|
|
126
|
+
*/
|
|
127
|
+
getBlocked(completed) {
|
|
128
|
+
const blocked = {};
|
|
129
|
+
for (const artifact of this.artifacts.values()) {
|
|
130
|
+
if (completed.has(artifact.id)) {
|
|
131
|
+
continue; // Already completed
|
|
132
|
+
}
|
|
133
|
+
const unmetDeps = artifact.requires.filter(req => !completed.has(req));
|
|
134
|
+
if (unmetDeps.length > 0) {
|
|
135
|
+
blocked[artifact.id] = unmetDeps.sort();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return blocked;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=graph.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { ArtifactSchema, SchemaYamlSchema, type Artifact, type SchemaYaml, type CompletedSet, type BlockedArtifacts, } from './types.js';
|
|
2
|
+
export { loadSchema, parseSchema, SchemaValidationError } from './schema.js';
|
|
3
|
+
export { ArtifactGraph } from './graph.js';
|
|
4
|
+
export { detectCompleted } from './state.js';
|
|
5
|
+
export { artifactOutputExists, isGlobPattern, resolveArtifactOutputs } from './outputs.js';
|
|
6
|
+
export { resolveSchema, listSchemas, listSchemasWithInfo, getSchemaDir, getPackageSchemasDir, getUserSchemasDir, SchemaLoadError, type SchemaInfo, } from './resolver.js';
|
|
7
|
+
export { loadTemplate, loadChangeContext, generateInstructions, formatChangeStatus, TemplateLoadError, type ChangeContext, type LoadChangeContextOptions, type ArtifactInstructions, type DependencyInfo, type ArtifactStatus, type ChangeStatus, type ArtifactPathSummary, } from './instruction-loader.js';
|
|
8
|
+
export type { PlanningHomeSummary, ActionContext, } from '../change-status-policy.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|