@dynamicworks/br-openspec 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +210 -0
- package/README.pt-BR.md +212 -0
- package/bin/openspec.js +3 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +484 -0
- package/dist/commands/change.d.ts +35 -0
- package/dist/commands/change.js +278 -0
- package/dist/commands/completion.d.ts +72 -0
- package/dist/commands/completion.js +258 -0
- package/dist/commands/config.d.ts +36 -0
- package/dist/commands/config.js +553 -0
- package/dist/commands/feedback.d.ts +9 -0
- package/dist/commands/feedback.js +184 -0
- package/dist/commands/schema.d.ts +6 -0
- package/dist/commands/schema.js +869 -0
- package/dist/commands/show.d.ts +14 -0
- package/dist/commands/show.js +133 -0
- package/dist/commands/spec.d.ts +15 -0
- package/dist/commands/spec.js +226 -0
- package/dist/commands/tools.d.ts +11 -0
- package/dist/commands/tools.js +252 -0
- package/dist/commands/validate.d.ts +24 -0
- package/dist/commands/validate.js +295 -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 +29 -0
- package/dist/commands/workflow/instructions.js +328 -0
- package/dist/commands/workflow/new-change.d.ts +11 -0
- package/dist/commands/workflow/new-change.js +44 -0
- package/dist/commands/workflow/schemas.d.ts +10 -0
- package/dist/commands/workflow/schemas.js +35 -0
- package/dist/commands/workflow/shared.d.ts +57 -0
- package/dist/commands/workflow/shared.js +117 -0
- package/dist/commands/workflow/status.d.ts +14 -0
- package/dist/commands/workflow/status.js +76 -0
- package/dist/commands/workflow/templates.d.ts +16 -0
- package/dist/commands/workflow/templates.js +70 -0
- package/dist/core/archive.d.ts +11 -0
- package/dist/core/archive.js +322 -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 +8 -0
- package/dist/core/artifact-graph/index.js +14 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +143 -0
- package/dist/core/artifact-graph/instruction-loader.js +217 -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 +258 -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 +45 -0
- package/dist/core/artifact-graph/types.js +43 -0
- package/dist/core/available-tools.d.ts +17 -0
- package/dist/core/available-tools.js +43 -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 +45 -0
- package/dist/core/command-generation/adapters/claude.d.ts +13 -0
- package/dist/core/command-generation/adapters/claude.js +50 -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 +44 -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 +55 -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 +51 -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/completions/command-registry.d.ts +7 -0
- package/dist/core/completions/command-registry.js +462 -0
- package/dist/core/completions/completion-provider.d.ts +60 -0
- package/dist/core/completions/completion-provider.js +102 -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 +32 -0
- package/dist/core/completions/generators/bash-generator.js +174 -0
- package/dist/core/completions/generators/fish-generator.d.ts +32 -0
- package/dist/core/completions/generators/fish-generator.js +157 -0
- package/dist/core/completions/generators/powershell-generator.d.ts +33 -0
- package/dist/core/completions/generators/powershell-generator.js +208 -0
- package/dist/core/completions/generators/zsh-generator.d.ts +44 -0
- package/dist/core/completions/generators/zsh-generator.js +250 -0
- package/dist/core/completions/installers/bash-installer.d.ts +87 -0
- package/dist/core/completions/installers/bash-installer.js +319 -0
- package/dist/core/completions/installers/fish-installer.d.ts +43 -0
- package/dist/core/completions/installers/fish-installer.js +143 -0
- package/dist/core/completions/installers/powershell-installer.d.ts +102 -0
- package/dist/core/completions/installers/powershell-installer.js +400 -0
- package/dist/core/completions/installers/zsh-installer.d.ts +125 -0
- package/dist/core/completions/installers/zsh-installer.js +450 -0
- package/dist/core/completions/templates/bash-templates.d.ts +6 -0
- package/dist/core/completions/templates/bash-templates.js +24 -0
- package/dist/core/completions/templates/fish-templates.d.ts +7 -0
- package/dist/core/completions/templates/fish-templates.js +39 -0
- package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
- package/dist/core/completions/templates/powershell-templates.js +25 -0
- package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
- package/dist/core/completions/templates/zsh-templates.js +36 -0
- package/dist/core/completions/types.d.ts +79 -0
- package/dist/core/completions/types.js +2 -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 +86 -0
- package/dist/core/config-schema.js +213 -0
- package/dist/core/config.d.ts +18 -0
- package/dist/core/config.js +38 -0
- package/dist/core/converters/json-converter.d.ts +6 -0
- package/dist/core/converters/json-converter.js +51 -0
- package/dist/core/global-config.d.ts +44 -0
- package/dist/core/global-config.js +125 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +3 -0
- package/dist/core/init.d.ts +37 -0
- package/dist/core/init.js +549 -0
- package/dist/core/is-project-initialized.d.ts +12 -0
- package/dist/core/is-project-initialized.js +18 -0
- package/dist/core/legacy-cleanup.d.ts +162 -0
- package/dist/core/legacy-cleanup.js +515 -0
- package/dist/core/list.d.ts +9 -0
- package/dist/core/list.js +172 -0
- package/dist/core/migration.d.ts +23 -0
- package/dist/core/migration.js +109 -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 +228 -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/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 +64 -0
- package/dist/core/project-config.js +224 -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 +73 -0
- package/dist/core/specs-apply.js +393 -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 +20 -0
- package/dist/core/templates/skill-templates.js +19 -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 +308 -0
- package/dist/core/templates/workflows/archive-change.d.ts +10 -0
- package/dist/core/templates/workflows/archive-change.js +271 -0
- package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
- package/dist/core/templates/workflows/bulk-archive-change.js +492 -0
- package/dist/core/templates/workflows/continue-change.d.ts +10 -0
- package/dist/core/templates/workflows/continue-change.js +232 -0
- package/dist/core/templates/workflows/explore.d.ts +10 -0
- package/dist/core/templates/workflows/explore.js +463 -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 +198 -0
- package/dist/core/templates/workflows/new-change.d.ts +10 -0
- package/dist/core/templates/workflows/new-change.js +21 -0
- package/dist/core/templates/workflows/onboard.d.ts +10 -0
- package/dist/core/templates/workflows/onboard.js +21 -0
- package/dist/core/templates/workflows/propose.d.ts +10 -0
- package/dist/core/templates/workflows/propose.js +216 -0
- package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
- package/dist/core/templates/workflows/sync-specs.js +272 -0
- package/dist/core/templates/workflows/upstream-sync.d.ts +10 -0
- package/dist/core/templates/workflows/upstream-sync.js +116 -0
- package/dist/core/templates/workflows/verify-change.d.ts +10 -0
- package/dist/core/templates/workflows/verify-change.js +21 -0
- package/dist/core/tools-manager.d.ts +56 -0
- package/dist/core/tools-manager.js +215 -0
- package/dist/core/update.d.ts +77 -0
- package/dist/core/update.js +538 -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 +33 -0
- package/dist/core/validation/validator.js +419 -0
- package/dist/core/view.d.ts +8 -0
- package/dist/core/view.js +169 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/messages/index.d.ts +867 -0
- package/dist/messages/index.js +1960 -0
- package/dist/prompts/searchable-multi-select.d.ts +28 -0
- package/dist/prompts/searchable-multi-select.js +160 -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 +165 -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 +147 -0
- package/dist/utils/change-metadata.d.ts +51 -0
- package/dist/utils/change-metadata.js +147 -0
- package/dist/utils/change-utils.d.ts +62 -0
- package/dist/utils/change-utils.js +121 -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 +302 -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 +37 -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,295 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { Validator } from '../core/validation/validator.js';
|
|
4
|
+
import { isInteractive, resolveNoInteractive } from '../utils/interactive.js';
|
|
5
|
+
import { getActiveChangeIds, getSpecIds } from '../utils/item-discovery.js';
|
|
6
|
+
import { nearestMatches } from '../utils/match.js';
|
|
7
|
+
import { VALIDATE_MESSAGES } from '../messages/index.js';
|
|
8
|
+
export class ValidateCommand {
|
|
9
|
+
async execute(itemName, options = {}) {
|
|
10
|
+
const interactive = isInteractive(options);
|
|
11
|
+
// Handle bulk flags first
|
|
12
|
+
if (options.all || options.changes || options.specs) {
|
|
13
|
+
await this.runBulkValidation({
|
|
14
|
+
changes: !!options.all || !!options.changes,
|
|
15
|
+
specs: !!options.all || !!options.specs,
|
|
16
|
+
}, { strict: !!options.strict, json: !!options.json, concurrency: options.concurrency, noInteractive: resolveNoInteractive(options) });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
// No item and no flags
|
|
20
|
+
if (!itemName) {
|
|
21
|
+
if (interactive) {
|
|
22
|
+
await this.runInteractiveSelector({ strict: !!options.strict, json: !!options.json, concurrency: options.concurrency });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
this.printNonInteractiveHint();
|
|
26
|
+
process.exitCode = 1;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Direct item validation with type detection or override
|
|
30
|
+
const typeOverride = this.normalizeType(options.type);
|
|
31
|
+
await this.validateDirectItem(itemName, { typeOverride, strict: !!options.strict, json: !!options.json });
|
|
32
|
+
}
|
|
33
|
+
normalizeType(value) {
|
|
34
|
+
if (!value)
|
|
35
|
+
return undefined;
|
|
36
|
+
const v = value.toLowerCase();
|
|
37
|
+
if (v === 'change' || v === 'spec')
|
|
38
|
+
return v;
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
async runInteractiveSelector(opts) {
|
|
42
|
+
const { select } = await import('@inquirer/prompts');
|
|
43
|
+
const choice = await select({
|
|
44
|
+
message: VALIDATE_MESSAGES.whatToValidate,
|
|
45
|
+
choices: [
|
|
46
|
+
{ name: VALIDATE_MESSAGES.optionAll, value: 'all' },
|
|
47
|
+
{ name: VALIDATE_MESSAGES.optionAllChanges, value: 'changes' },
|
|
48
|
+
{ name: VALIDATE_MESSAGES.optionAllSpecs, value: 'specs' },
|
|
49
|
+
{ name: VALIDATE_MESSAGES.optionPickOne, value: 'one' },
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
if (choice === 'all')
|
|
53
|
+
return this.runBulkValidation({ changes: true, specs: true }, opts);
|
|
54
|
+
if (choice === 'changes')
|
|
55
|
+
return this.runBulkValidation({ changes: true, specs: false }, opts);
|
|
56
|
+
if (choice === 'specs')
|
|
57
|
+
return this.runBulkValidation({ changes: false, specs: true }, opts);
|
|
58
|
+
// one
|
|
59
|
+
const [changes, specs] = await Promise.all([getActiveChangeIds(), getSpecIds()]);
|
|
60
|
+
const items = [];
|
|
61
|
+
items.push(...changes.map(id => ({ name: `change/${id}`, value: { type: 'change', id } })));
|
|
62
|
+
items.push(...specs.map(id => ({ name: `spec/${id}`, value: { type: 'spec', id } })));
|
|
63
|
+
if (items.length === 0) {
|
|
64
|
+
console.error(VALIDATE_MESSAGES.noItemsToValidate);
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const picked = await select({ message: VALIDATE_MESSAGES.pickAnItem, choices: items });
|
|
69
|
+
await this.validateByType(picked.type, picked.id, opts);
|
|
70
|
+
}
|
|
71
|
+
printNonInteractiveHint() {
|
|
72
|
+
console.error(VALIDATE_MESSAGES.nothingToValidate);
|
|
73
|
+
console.error(VALIDATE_MESSAGES.validateAllHint);
|
|
74
|
+
console.error(VALIDATE_MESSAGES.validateChangesHint);
|
|
75
|
+
console.error(VALIDATE_MESSAGES.validateSpecsHint);
|
|
76
|
+
console.error(VALIDATE_MESSAGES.validateItemHint);
|
|
77
|
+
console.error(VALIDATE_MESSAGES.runInteractiveHint);
|
|
78
|
+
}
|
|
79
|
+
async validateDirectItem(itemName, opts) {
|
|
80
|
+
const [changes, specs] = await Promise.all([getActiveChangeIds(), getSpecIds()]);
|
|
81
|
+
const isChange = changes.includes(itemName);
|
|
82
|
+
const isSpec = specs.includes(itemName);
|
|
83
|
+
const type = opts.typeOverride ?? (isChange ? 'change' : isSpec ? 'spec' : undefined);
|
|
84
|
+
if (!type) {
|
|
85
|
+
console.error(VALIDATE_MESSAGES.unknownItem(itemName));
|
|
86
|
+
const suggestions = nearestMatches(itemName, [...changes, ...specs]);
|
|
87
|
+
if (suggestions.length)
|
|
88
|
+
console.error(VALIDATE_MESSAGES.didYouMean(suggestions.join(', ')));
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!opts.typeOverride && isChange && isSpec) {
|
|
93
|
+
console.error(VALIDATE_MESSAGES.ambiguousItem(itemName));
|
|
94
|
+
console.error(VALIDATE_MESSAGES.passTypeHint);
|
|
95
|
+
process.exitCode = 1;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
await this.validateByType(type, itemName, opts);
|
|
99
|
+
}
|
|
100
|
+
async validateByType(type, id, opts) {
|
|
101
|
+
const validator = new Validator(opts.strict);
|
|
102
|
+
if (type === 'change') {
|
|
103
|
+
const changeDir = path.join(process.cwd(), 'openspec', 'changes', id);
|
|
104
|
+
const start = Date.now();
|
|
105
|
+
const report = await validator.validateChangeDeltaSpecs(changeDir);
|
|
106
|
+
const durationMs = Date.now() - start;
|
|
107
|
+
this.printReport('change', id, report, durationMs, opts.json);
|
|
108
|
+
// Non-zero exit if invalid (keeps enriched output test semantics)
|
|
109
|
+
process.exitCode = report.valid ? 0 : 1;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const file = path.join(process.cwd(), 'openspec', 'specs', id, 'spec.md');
|
|
113
|
+
const start = Date.now();
|
|
114
|
+
const report = await validator.validateSpec(file);
|
|
115
|
+
const durationMs = Date.now() - start;
|
|
116
|
+
this.printReport('spec', id, report, durationMs, opts.json);
|
|
117
|
+
process.exitCode = report.valid ? 0 : 1;
|
|
118
|
+
}
|
|
119
|
+
printReport(type, id, report, durationMs, json) {
|
|
120
|
+
if (json) {
|
|
121
|
+
const out = { items: [{ id, type, valid: report.valid, issues: report.issues, durationMs }], summary: { totals: { items: 1, passed: report.valid ? 1 : 0, failed: report.valid ? 0 : 1 }, byType: { [type]: { items: 1, passed: report.valid ? 1 : 0, failed: report.valid ? 0 : 1 } } }, version: '1.0' };
|
|
122
|
+
console.log(JSON.stringify(out, null, 2));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (report.valid) {
|
|
126
|
+
console.log(type === 'change' ? VALIDATE_MESSAGES.changeIsValid(id) : VALIDATE_MESSAGES.specIsValid(id));
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
console.error(type === 'change' ? VALIDATE_MESSAGES.changeHasIssues(id) : VALIDATE_MESSAGES.specHasIssues(id));
|
|
130
|
+
for (const issue of report.issues) {
|
|
131
|
+
const label = issue.level === 'ERROR' ? 'ERROR' : issue.level;
|
|
132
|
+
const prefix = issue.level === 'ERROR' ? '✗' : issue.level === 'WARNING' ? '⚠' : 'ℹ';
|
|
133
|
+
console.error(`${prefix} [${label}] ${issue.path}: ${issue.message}`);
|
|
134
|
+
}
|
|
135
|
+
this.printNextSteps(type);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
printNextSteps(type) {
|
|
139
|
+
const bullets = [];
|
|
140
|
+
if (type === 'change') {
|
|
141
|
+
bullets.push(VALIDATE_MESSAGES.ensureDeltasInSpecs);
|
|
142
|
+
bullets.push(VALIDATE_MESSAGES.eachRequirementNeedsScenario);
|
|
143
|
+
bullets.push(VALIDATE_MESSAGES.debugParsedDeltas);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
bullets.push(VALIDATE_MESSAGES.ensurePurposeAndRequirements);
|
|
147
|
+
bullets.push(VALIDATE_MESSAGES.requirementScenarioBullet);
|
|
148
|
+
bullets.push(VALIDATE_MESSAGES.rerunWithJson);
|
|
149
|
+
}
|
|
150
|
+
console.error(type === 'change' ? VALIDATE_MESSAGES.nextStepsChange : VALIDATE_MESSAGES.nextStepsSpec);
|
|
151
|
+
bullets.forEach(b => console.error(` ${b}`));
|
|
152
|
+
}
|
|
153
|
+
async runBulkValidation(scope, opts) {
|
|
154
|
+
const spinner = !opts.json && !opts.noInteractive ? ora(VALIDATE_MESSAGES.validating).start() : undefined;
|
|
155
|
+
const [changeIds, specIds] = await Promise.all([
|
|
156
|
+
scope.changes ? getActiveChangeIds() : Promise.resolve([]),
|
|
157
|
+
scope.specs ? getSpecIds() : Promise.resolve([]),
|
|
158
|
+
]);
|
|
159
|
+
const DEFAULT_CONCURRENCY = 6;
|
|
160
|
+
const maxSuggestions = 5; // used by nearestMatches
|
|
161
|
+
const concurrency = normalizeConcurrency(opts.concurrency) ?? normalizeConcurrency(process.env.OPENSPEC_CONCURRENCY) ?? DEFAULT_CONCURRENCY;
|
|
162
|
+
const validator = new Validator(opts.strict);
|
|
163
|
+
const queue = [];
|
|
164
|
+
for (const id of changeIds) {
|
|
165
|
+
queue.push(async () => {
|
|
166
|
+
const start = Date.now();
|
|
167
|
+
const changeDir = path.join(process.cwd(), 'openspec', 'changes', id);
|
|
168
|
+
const report = await validator.validateChangeDeltaSpecs(changeDir);
|
|
169
|
+
const durationMs = Date.now() - start;
|
|
170
|
+
return { id, type: 'change', valid: report.valid, issues: report.issues, durationMs };
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
for (const id of specIds) {
|
|
174
|
+
queue.push(async () => {
|
|
175
|
+
const start = Date.now();
|
|
176
|
+
const file = path.join(process.cwd(), 'openspec', 'specs', id, 'spec.md');
|
|
177
|
+
const report = await validator.validateSpec(file);
|
|
178
|
+
const durationMs = Date.now() - start;
|
|
179
|
+
return { id, type: 'spec', valid: report.valid, issues: report.issues, durationMs };
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (queue.length === 0) {
|
|
183
|
+
spinner?.stop();
|
|
184
|
+
const summary = {
|
|
185
|
+
totals: { items: 0, passed: 0, failed: 0 },
|
|
186
|
+
byType: {
|
|
187
|
+
...(scope.changes ? { change: { items: 0, passed: 0, failed: 0 } } : {}),
|
|
188
|
+
...(scope.specs ? { spec: { items: 0, passed: 0, failed: 0 } } : {}),
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
if (opts.json) {
|
|
192
|
+
const out = { items: [], summary, version: '1.0' };
|
|
193
|
+
console.log(JSON.stringify(out, null, 2));
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
console.log(VALIDATE_MESSAGES.noItemsFoundToValidate);
|
|
197
|
+
}
|
|
198
|
+
process.exitCode = 0;
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const results = [];
|
|
202
|
+
let index = 0;
|
|
203
|
+
let running = 0;
|
|
204
|
+
let passed = 0;
|
|
205
|
+
let failed = 0;
|
|
206
|
+
await new Promise((resolve) => {
|
|
207
|
+
const next = () => {
|
|
208
|
+
while (running < concurrency && index < queue.length) {
|
|
209
|
+
const currentIndex = index++;
|
|
210
|
+
const task = queue[currentIndex];
|
|
211
|
+
running++;
|
|
212
|
+
if (spinner)
|
|
213
|
+
spinner.text = VALIDATE_MESSAGES.validatingProgress(currentIndex + 1, queue.length);
|
|
214
|
+
task()
|
|
215
|
+
.then(res => {
|
|
216
|
+
results.push(res);
|
|
217
|
+
if (res.valid)
|
|
218
|
+
passed++;
|
|
219
|
+
else
|
|
220
|
+
failed++;
|
|
221
|
+
})
|
|
222
|
+
.catch((error) => {
|
|
223
|
+
const message = error?.message || 'Unknown error';
|
|
224
|
+
const res = { id: getPlannedId(currentIndex, changeIds, specIds) ?? 'unknown', type: getPlannedType(currentIndex, changeIds, specIds) ?? 'change', valid: false, issues: [{ level: 'ERROR', path: 'file', message }], durationMs: 0 };
|
|
225
|
+
results.push(res);
|
|
226
|
+
failed++;
|
|
227
|
+
})
|
|
228
|
+
.finally(() => {
|
|
229
|
+
running--;
|
|
230
|
+
if (index >= queue.length && running === 0)
|
|
231
|
+
resolve();
|
|
232
|
+
else
|
|
233
|
+
next();
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
next();
|
|
238
|
+
});
|
|
239
|
+
spinner?.stop();
|
|
240
|
+
results.sort((a, b) => a.id.localeCompare(b.id));
|
|
241
|
+
const summary = {
|
|
242
|
+
totals: { items: results.length, passed, failed },
|
|
243
|
+
byType: {
|
|
244
|
+
...(scope.changes ? { change: summarizeType(results, 'change') } : {}),
|
|
245
|
+
...(scope.specs ? { spec: summarizeType(results, 'spec') } : {}),
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
if (opts.json) {
|
|
249
|
+
const out = { items: results, summary, version: '1.0' };
|
|
250
|
+
console.log(JSON.stringify(out, null, 2));
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
for (const res of results) {
|
|
254
|
+
if (res.valid)
|
|
255
|
+
console.log(`✓ ${res.type}/${res.id}`);
|
|
256
|
+
else
|
|
257
|
+
console.error(`✗ ${res.type}/${res.id}`);
|
|
258
|
+
}
|
|
259
|
+
console.log(VALIDATE_MESSAGES.totals(summary.totals.passed, summary.totals.failed, summary.totals.items));
|
|
260
|
+
}
|
|
261
|
+
process.exitCode = failed > 0 ? 1 : 0;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function summarizeType(results, type) {
|
|
265
|
+
const filtered = results.filter(r => r.type === type);
|
|
266
|
+
const items = filtered.length;
|
|
267
|
+
const passed = filtered.filter(r => r.valid).length;
|
|
268
|
+
const failed = items - passed;
|
|
269
|
+
return { items, passed, failed };
|
|
270
|
+
}
|
|
271
|
+
function normalizeConcurrency(value) {
|
|
272
|
+
if (!value)
|
|
273
|
+
return undefined;
|
|
274
|
+
const n = parseInt(value, 10);
|
|
275
|
+
if (Number.isNaN(n) || n <= 0)
|
|
276
|
+
return undefined;
|
|
277
|
+
return n;
|
|
278
|
+
}
|
|
279
|
+
function getPlannedId(index, changeIds, specIds) {
|
|
280
|
+
const totalChanges = changeIds.length;
|
|
281
|
+
if (index < totalChanges)
|
|
282
|
+
return changeIds[index];
|
|
283
|
+
const specIndex = index - totalChanges;
|
|
284
|
+
return specIds[specIndex];
|
|
285
|
+
}
|
|
286
|
+
function getPlannedType(index, changeIds, specIds) {
|
|
287
|
+
const totalChanges = changeIds.length;
|
|
288
|
+
if (index < totalChanges)
|
|
289
|
+
return 'change';
|
|
290
|
+
const specIndex = index - totalChanges;
|
|
291
|
+
if (specIndex >= 0 && specIndex < specIds.length)
|
|
292
|
+
return 'spec';
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow CLI Commands
|
|
3
|
+
*
|
|
4
|
+
* Commands for the artifact-driven workflow: status, instructions, templates, schemas, new change.
|
|
5
|
+
*/
|
|
6
|
+
export { statusCommand } from './status.js';
|
|
7
|
+
export type { StatusOptions } from './status.js';
|
|
8
|
+
export { instructionsCommand, applyInstructionsCommand } from './instructions.js';
|
|
9
|
+
export type { InstructionsOptions } from './instructions.js';
|
|
10
|
+
export { templatesCommand } from './templates.js';
|
|
11
|
+
export type { TemplatesOptions } from './templates.js';
|
|
12
|
+
export { schemasCommand } from './schemas.js';
|
|
13
|
+
export type { SchemasOptions } from './schemas.js';
|
|
14
|
+
export { newChangeCommand } from './new-change.js';
|
|
15
|
+
export type { NewChangeOptions } from './new-change.js';
|
|
16
|
+
export { DEFAULT_SCHEMA } from './shared.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow CLI Commands
|
|
3
|
+
*
|
|
4
|
+
* Commands for the artifact-driven workflow: status, instructions, templates, schemas, new change.
|
|
5
|
+
*/
|
|
6
|
+
export { statusCommand } from './status.js';
|
|
7
|
+
export { instructionsCommand, applyInstructionsCommand } from './instructions.js';
|
|
8
|
+
export { templatesCommand } from './templates.js';
|
|
9
|
+
export { schemasCommand } from './schemas.js';
|
|
10
|
+
export { newChangeCommand } from './new-change.js';
|
|
11
|
+
export { DEFAULT_SCHEMA } from './shared.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instructions Command
|
|
3
|
+
*
|
|
4
|
+
* Generates enriched instructions for creating artifacts or applying tasks.
|
|
5
|
+
* Includes both artifact instructions and apply instructions.
|
|
6
|
+
*/
|
|
7
|
+
import { type ArtifactInstructions } from '../../core/artifact-graph/index.js';
|
|
8
|
+
import { type ApplyInstructions } from './shared.js';
|
|
9
|
+
export interface InstructionsOptions {
|
|
10
|
+
change?: string;
|
|
11
|
+
schema?: string;
|
|
12
|
+
json?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface ApplyInstructionsOptions {
|
|
15
|
+
change?: string;
|
|
16
|
+
schema?: string;
|
|
17
|
+
json?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function instructionsCommand(artifactId: string | undefined, options: InstructionsOptions): Promise<void>;
|
|
20
|
+
export declare function printInstructionsText(instructions: ArtifactInstructions, isBlocked: boolean): void;
|
|
21
|
+
/**
|
|
22
|
+
* Generates apply instructions for implementing tasks from a change.
|
|
23
|
+
* Schema-aware: reads apply phase configuration from schema to determine
|
|
24
|
+
* required artifacts, tracking file, and instruction.
|
|
25
|
+
*/
|
|
26
|
+
export declare function generateApplyInstructions(projectRoot: string, changeName: string, schemaName?: string): Promise<ApplyInstructions>;
|
|
27
|
+
export declare function applyInstructionsCommand(options: ApplyInstructionsOptions): Promise<void>;
|
|
28
|
+
export declare function printApplyInstructionsText(instructions: ApplyInstructions): void;
|
|
29
|
+
//# sourceMappingURL=instructions.d.ts.map
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instructions Command
|
|
3
|
+
*
|
|
4
|
+
* Generates enriched instructions for creating artifacts or applying tasks.
|
|
5
|
+
* Includes both artifact instructions and apply instructions.
|
|
6
|
+
*/
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import { loadChangeContext, generateInstructions, resolveSchema, resolveArtifactOutputs, } from '../../core/artifact-graph/index.js';
|
|
11
|
+
import { validateChangeExists, validateSchemaExists, } from './shared.js';
|
|
12
|
+
import { WORKFLOW_MESSAGES } from '../../messages/index.js';
|
|
13
|
+
// -----------------------------------------------------------------------------
|
|
14
|
+
// Artifact Instructions Command
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
export async function instructionsCommand(artifactId, options) {
|
|
17
|
+
const spinner = options.json ? undefined : ora(WORKFLOW_MESSAGES.generatingInstructions).start();
|
|
18
|
+
try {
|
|
19
|
+
const projectRoot = process.cwd();
|
|
20
|
+
const changeName = await validateChangeExists(options.change, projectRoot);
|
|
21
|
+
// Validate schema if explicitly provided
|
|
22
|
+
if (options.schema) {
|
|
23
|
+
validateSchemaExists(options.schema, projectRoot);
|
|
24
|
+
}
|
|
25
|
+
// loadChangeContext will auto-detect schema from metadata if not provided
|
|
26
|
+
const context = loadChangeContext(projectRoot, changeName, options.schema);
|
|
27
|
+
if (!artifactId) {
|
|
28
|
+
spinner?.stop();
|
|
29
|
+
const validIds = context.graph.getAllArtifacts().map((a) => a.id);
|
|
30
|
+
throw new Error(WORKFLOW_MESSAGES.missingArtifactArgument(validIds.join('\n ')));
|
|
31
|
+
}
|
|
32
|
+
const artifact = context.graph.getArtifact(artifactId);
|
|
33
|
+
if (!artifact) {
|
|
34
|
+
spinner?.stop();
|
|
35
|
+
const validIds = context.graph.getAllArtifacts().map((a) => a.id);
|
|
36
|
+
throw new Error(WORKFLOW_MESSAGES.artifactNotFound(artifactId, context.schemaName, validIds.join('\n ')));
|
|
37
|
+
}
|
|
38
|
+
const instructions = generateInstructions(context, artifactId, projectRoot);
|
|
39
|
+
const isBlocked = instructions.dependencies.some((d) => !d.done);
|
|
40
|
+
spinner?.stop();
|
|
41
|
+
if (options.json) {
|
|
42
|
+
console.log(JSON.stringify(instructions, null, 2));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
printInstructionsText(instructions, isBlocked);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
spinner?.stop();
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function printInstructionsText(instructions, isBlocked) {
|
|
53
|
+
const { artifactId, changeName, schemaName, changeDir, outputPath, description, instruction, context, rules, template, dependencies, unlocks, } = instructions;
|
|
54
|
+
// Opening tag
|
|
55
|
+
console.log(`<artifact id="${artifactId}" change="${changeName}" schema="${schemaName}">`);
|
|
56
|
+
console.log();
|
|
57
|
+
// Warning for blocked artifacts
|
|
58
|
+
if (isBlocked) {
|
|
59
|
+
const missing = dependencies.filter((d) => !d.done).map((d) => d.id);
|
|
60
|
+
console.log('<warning>');
|
|
61
|
+
console.log(WORKFLOW_MESSAGES.unmetDependenciesWarning);
|
|
62
|
+
console.log(WORKFLOW_MESSAGES.missingDependencies(missing.join(', ')));
|
|
63
|
+
console.log('</warning>');
|
|
64
|
+
console.log();
|
|
65
|
+
}
|
|
66
|
+
// Task directive
|
|
67
|
+
console.log('<task>');
|
|
68
|
+
console.log(WORKFLOW_MESSAGES.createArtifactTask(artifactId, changeName));
|
|
69
|
+
console.log(description);
|
|
70
|
+
console.log('</task>');
|
|
71
|
+
console.log();
|
|
72
|
+
// Project context (AI constraint - do not include in output)
|
|
73
|
+
if (context) {
|
|
74
|
+
console.log('<project_context>');
|
|
75
|
+
console.log('<!-- This is background information for you. Do NOT include this in your output. -->');
|
|
76
|
+
console.log(context);
|
|
77
|
+
console.log('</project_context>');
|
|
78
|
+
console.log();
|
|
79
|
+
}
|
|
80
|
+
// Rules (AI constraint - do not include in output)
|
|
81
|
+
if (rules && rules.length > 0) {
|
|
82
|
+
console.log('<rules>');
|
|
83
|
+
console.log('<!-- These are constraints for you to follow. Do NOT include this in your output. -->');
|
|
84
|
+
for (const rule of rules) {
|
|
85
|
+
console.log(`- ${rule}`);
|
|
86
|
+
}
|
|
87
|
+
console.log('</rules>');
|
|
88
|
+
console.log();
|
|
89
|
+
}
|
|
90
|
+
// Dependencies (files to read for context)
|
|
91
|
+
if (dependencies.length > 0) {
|
|
92
|
+
console.log('<dependencies>');
|
|
93
|
+
console.log(WORKFLOW_MESSAGES.readFilesForContext);
|
|
94
|
+
console.log();
|
|
95
|
+
for (const dep of dependencies) {
|
|
96
|
+
const status = dep.done ? 'done' : 'missing';
|
|
97
|
+
const fullPath = path.join(changeDir, dep.path);
|
|
98
|
+
console.log(`<dependency id="${dep.id}" status="${status}">`);
|
|
99
|
+
console.log(` <path>${fullPath}</path>`);
|
|
100
|
+
console.log(` <description>${dep.description}</description>`);
|
|
101
|
+
console.log('</dependency>');
|
|
102
|
+
}
|
|
103
|
+
console.log('</dependencies>');
|
|
104
|
+
console.log();
|
|
105
|
+
}
|
|
106
|
+
// Output location
|
|
107
|
+
console.log('<output>');
|
|
108
|
+
console.log(WORKFLOW_MESSAGES.writeTo(path.join(changeDir, outputPath)));
|
|
109
|
+
console.log('</output>');
|
|
110
|
+
console.log();
|
|
111
|
+
// Instruction (guidance)
|
|
112
|
+
if (instruction) {
|
|
113
|
+
console.log('<instruction>');
|
|
114
|
+
console.log(instruction.trim());
|
|
115
|
+
console.log('</instruction>');
|
|
116
|
+
console.log();
|
|
117
|
+
}
|
|
118
|
+
// Template
|
|
119
|
+
console.log('<template>');
|
|
120
|
+
console.log('<!-- Use this as the structure for your output file. Fill in the sections. -->');
|
|
121
|
+
console.log(template.trim());
|
|
122
|
+
console.log('</template>');
|
|
123
|
+
console.log();
|
|
124
|
+
// Success criteria placeholder
|
|
125
|
+
console.log('<success_criteria>');
|
|
126
|
+
console.log('<!-- To be defined in schema validation rules -->');
|
|
127
|
+
console.log('</success_criteria>');
|
|
128
|
+
console.log();
|
|
129
|
+
// Unlocks
|
|
130
|
+
if (unlocks.length > 0) {
|
|
131
|
+
console.log('<unlocks>');
|
|
132
|
+
console.log(WORKFLOW_MESSAGES.unlocksArtifacts(unlocks.join(', ')));
|
|
133
|
+
console.log('</unlocks>');
|
|
134
|
+
console.log();
|
|
135
|
+
}
|
|
136
|
+
// Closing tag
|
|
137
|
+
console.log('</artifact>');
|
|
138
|
+
}
|
|
139
|
+
// -----------------------------------------------------------------------------
|
|
140
|
+
// Apply Instructions Command
|
|
141
|
+
// -----------------------------------------------------------------------------
|
|
142
|
+
/**
|
|
143
|
+
* Parses tasks.md content and extracts task items with their completion status.
|
|
144
|
+
*/
|
|
145
|
+
function parseTasksFile(content) {
|
|
146
|
+
const tasks = [];
|
|
147
|
+
const lines = content.split('\n');
|
|
148
|
+
let taskIndex = 0;
|
|
149
|
+
for (const line of lines) {
|
|
150
|
+
// Match checkbox patterns: - [ ] or - [x] or - [X]
|
|
151
|
+
const checkboxMatch = line.match(/^[-*]\s*\[([ xX])\]\s*(.+)\s*$/);
|
|
152
|
+
if (checkboxMatch) {
|
|
153
|
+
taskIndex++;
|
|
154
|
+
const done = checkboxMatch[1].toLowerCase() === 'x';
|
|
155
|
+
const description = checkboxMatch[2].trim();
|
|
156
|
+
tasks.push({
|
|
157
|
+
id: `${taskIndex}`,
|
|
158
|
+
description,
|
|
159
|
+
done,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return tasks;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Generates apply instructions for implementing tasks from a change.
|
|
167
|
+
* Schema-aware: reads apply phase configuration from schema to determine
|
|
168
|
+
* required artifacts, tracking file, and instruction.
|
|
169
|
+
*/
|
|
170
|
+
export async function generateApplyInstructions(projectRoot, changeName, schemaName) {
|
|
171
|
+
// loadChangeContext will auto-detect schema from metadata if not provided
|
|
172
|
+
const context = loadChangeContext(projectRoot, changeName, schemaName);
|
|
173
|
+
const changeDir = context.changeDir;
|
|
174
|
+
// Get the full schema to access the apply phase configuration
|
|
175
|
+
const schema = resolveSchema(context.schemaName, projectRoot);
|
|
176
|
+
const applyConfig = schema.apply;
|
|
177
|
+
// Determine required artifacts and tracking file from schema
|
|
178
|
+
// Fallback: if no apply block, require all artifacts
|
|
179
|
+
const requiredArtifactIds = applyConfig?.requires ?? schema.artifacts.map((a) => a.id);
|
|
180
|
+
const tracksFile = applyConfig?.tracks ?? null;
|
|
181
|
+
const schemaInstruction = applyConfig?.instruction ?? null;
|
|
182
|
+
// Check which required artifacts are missing
|
|
183
|
+
const missingArtifacts = [];
|
|
184
|
+
for (const artifactId of requiredArtifactIds) {
|
|
185
|
+
const artifact = schema.artifacts.find((a) => a.id === artifactId);
|
|
186
|
+
if (artifact && resolveArtifactOutputs(changeDir, artifact.generates).length === 0) {
|
|
187
|
+
missingArtifacts.push(artifactId);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Build context files from all existing artifacts in schema
|
|
191
|
+
const contextFiles = {};
|
|
192
|
+
for (const artifact of schema.artifacts) {
|
|
193
|
+
const outputs = resolveArtifactOutputs(changeDir, artifact.generates);
|
|
194
|
+
if (outputs.length > 0) {
|
|
195
|
+
contextFiles[artifact.id] = outputs;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Parse tasks if tracking file exists
|
|
199
|
+
let tasks = [];
|
|
200
|
+
let tracksFileExists = false;
|
|
201
|
+
if (tracksFile) {
|
|
202
|
+
const tracksPath = path.join(changeDir, tracksFile);
|
|
203
|
+
tracksFileExists = fs.existsSync(tracksPath);
|
|
204
|
+
if (tracksFileExists) {
|
|
205
|
+
const tasksContent = await fs.promises.readFile(tracksPath, 'utf-8');
|
|
206
|
+
tasks = parseTasksFile(tasksContent);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Calculate progress
|
|
210
|
+
const total = tasks.length;
|
|
211
|
+
const complete = tasks.filter((t) => t.done).length;
|
|
212
|
+
const remaining = total - complete;
|
|
213
|
+
// Determine state and instruction
|
|
214
|
+
let state;
|
|
215
|
+
let instruction;
|
|
216
|
+
if (missingArtifacts.length > 0) {
|
|
217
|
+
state = 'blocked';
|
|
218
|
+
instruction = WORKFLOW_MESSAGES.cannotApplyMissingArtifacts(missingArtifacts.join(', '));
|
|
219
|
+
}
|
|
220
|
+
else if (tracksFile && !tracksFileExists) {
|
|
221
|
+
// Tracking file configured but doesn't exist yet
|
|
222
|
+
const tracksFilename = path.basename(tracksFile);
|
|
223
|
+
state = 'blocked';
|
|
224
|
+
instruction = WORKFLOW_MESSAGES.missingTrackingFile(tracksFilename);
|
|
225
|
+
}
|
|
226
|
+
else if (tracksFile && tracksFileExists && total === 0) {
|
|
227
|
+
// Tracking file exists but contains no tasks
|
|
228
|
+
const tracksFilename = path.basename(tracksFile);
|
|
229
|
+
state = 'blocked';
|
|
230
|
+
instruction = WORKFLOW_MESSAGES.trackingFileNoTasks(tracksFilename);
|
|
231
|
+
}
|
|
232
|
+
else if (tracksFile && remaining === 0 && total > 0) {
|
|
233
|
+
state = 'all_done';
|
|
234
|
+
instruction = WORKFLOW_MESSAGES.allTasksComplete;
|
|
235
|
+
}
|
|
236
|
+
else if (!tracksFile) {
|
|
237
|
+
// No tracking file configured in schema - ready to apply
|
|
238
|
+
state = 'ready';
|
|
239
|
+
instruction = schemaInstruction?.trim() ?? WORKFLOW_MESSAGES.allArtifactsCompleteProceed;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
state = 'ready';
|
|
243
|
+
instruction = schemaInstruction?.trim() ?? WORKFLOW_MESSAGES.readContextAndWorkTasks;
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
changeName,
|
|
247
|
+
changeDir,
|
|
248
|
+
schemaName: context.schemaName,
|
|
249
|
+
contextFiles,
|
|
250
|
+
progress: { total, complete, remaining },
|
|
251
|
+
tasks,
|
|
252
|
+
state,
|
|
253
|
+
missingArtifacts: missingArtifacts.length > 0 ? missingArtifacts : undefined,
|
|
254
|
+
instruction,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
export async function applyInstructionsCommand(options) {
|
|
258
|
+
const spinner = options.json ? undefined : ora(WORKFLOW_MESSAGES.generatingApplyInstructions).start();
|
|
259
|
+
try {
|
|
260
|
+
const projectRoot = process.cwd();
|
|
261
|
+
const changeName = await validateChangeExists(options.change, projectRoot);
|
|
262
|
+
// Validate schema if explicitly provided
|
|
263
|
+
if (options.schema) {
|
|
264
|
+
validateSchemaExists(options.schema, projectRoot);
|
|
265
|
+
}
|
|
266
|
+
// generateApplyInstructions uses loadChangeContext which auto-detects schema
|
|
267
|
+
const instructions = await generateApplyInstructions(projectRoot, changeName, options.schema);
|
|
268
|
+
spinner?.stop();
|
|
269
|
+
if (options.json) {
|
|
270
|
+
console.log(JSON.stringify(instructions, null, 2));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
printApplyInstructionsText(instructions);
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
spinner?.stop();
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
export function printApplyInstructionsText(instructions) {
|
|
281
|
+
const { changeName, schemaName, contextFiles, progress, tasks, state, missingArtifacts, instruction } = instructions;
|
|
282
|
+
console.log(WORKFLOW_MESSAGES.applyTitle(changeName));
|
|
283
|
+
console.log(WORKFLOW_MESSAGES.schemaLabel(schemaName));
|
|
284
|
+
console.log();
|
|
285
|
+
// Warning for blocked state
|
|
286
|
+
if (state === 'blocked' && missingArtifacts) {
|
|
287
|
+
console.log(WORKFLOW_MESSAGES.blockedTitle);
|
|
288
|
+
console.log();
|
|
289
|
+
console.log(WORKFLOW_MESSAGES.missingArtifactsLabel(missingArtifacts.join(', ')));
|
|
290
|
+
console.log(WORKFLOW_MESSAGES.createMissingFirst);
|
|
291
|
+
console.log();
|
|
292
|
+
}
|
|
293
|
+
// Context files (dynamically from schema)
|
|
294
|
+
const contextFileEntries = Object.entries(contextFiles);
|
|
295
|
+
if (contextFileEntries.length > 0) {
|
|
296
|
+
console.log(WORKFLOW_MESSAGES.contextFilesTitle);
|
|
297
|
+
for (const [artifactId, filePaths] of contextFileEntries) {
|
|
298
|
+
for (const filePath of filePaths) {
|
|
299
|
+
console.log(`- ${artifactId}: ${filePath}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
console.log();
|
|
303
|
+
}
|
|
304
|
+
// Progress (only show if we have tracking)
|
|
305
|
+
if (progress.total > 0 || tasks.length > 0) {
|
|
306
|
+
console.log(WORKFLOW_MESSAGES.progressTitle);
|
|
307
|
+
if (state === 'all_done') {
|
|
308
|
+
console.log(WORKFLOW_MESSAGES.progressCompleteWithCheck(progress.complete, progress.total));
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
console.log(WORKFLOW_MESSAGES.progressComplete(progress.complete, progress.total));
|
|
312
|
+
}
|
|
313
|
+
console.log();
|
|
314
|
+
}
|
|
315
|
+
// Tasks
|
|
316
|
+
if (tasks.length > 0) {
|
|
317
|
+
console.log(WORKFLOW_MESSAGES.tasksTitle);
|
|
318
|
+
for (const task of tasks) {
|
|
319
|
+
const checkbox = task.done ? '[x]' : '[ ]';
|
|
320
|
+
console.log(`- ${checkbox} ${task.description}`);
|
|
321
|
+
}
|
|
322
|
+
console.log();
|
|
323
|
+
}
|
|
324
|
+
// Instruction
|
|
325
|
+
console.log(WORKFLOW_MESSAGES.instructionTitle);
|
|
326
|
+
console.log(instruction);
|
|
327
|
+
}
|
|
328
|
+
//# sourceMappingURL=instructions.js.map
|