@codewalla_india/openspec 1.3.0 → 1.3.2
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 -22
- package/README.md +272 -230
- package/bin/openspec.js +5 -5
- package/dist/cli/index.js +99 -62
- package/dist/commands/change.d.ts +0 -2
- package/dist/commands/change.js +107 -86
- package/dist/commands/config.js +14 -16
- package/dist/commands/doctor.js +9 -4
- package/dist/commands/feedback.js +79 -30
- package/dist/commands/modify.d.ts +26 -0
- package/dist/commands/modify.js +147 -0
- package/dist/commands/schema.js +170 -91
- package/dist/commands/show.js +5 -2
- package/dist/commands/spec.js +49 -29
- package/dist/commands/store.js +15 -21
- package/dist/commands/validate.d.ts +8 -0
- package/dist/commands/validate.js +43 -20
- package/dist/commands/workflow/index.d.ts +2 -2
- package/dist/commands/workflow/index.js +1 -1
- package/dist/commands/workflow/instructions.d.ts +6 -26
- package/dist/commands/workflow/instructions.js +183 -376
- package/dist/commands/workflow/new-change.d.ts +0 -4
- package/dist/commands/workflow/new-change.js +10 -28
- package/dist/commands/workflow/shared.d.ts +34 -19
- package/dist/commands/workflow/shared.js +34 -4
- package/dist/commands/workflow/status.js +10 -32
- package/dist/commands/workflow/templates.js +15 -5
- package/dist/commands/workset.d.ts +2 -2
- package/dist/commands/workset.js +19 -22
- package/dist/comprehension-quiz/fingerprint.d.ts +39 -0
- package/dist/comprehension-quiz/fingerprint.js +84 -0
- package/dist/comprehension-quiz/index.d.ts +42 -0
- package/dist/comprehension-quiz/index.js +47 -0
- package/dist/comprehension-quiz/pass-record.d.ts +44 -0
- package/dist/comprehension-quiz/pass-record.js +98 -0
- package/dist/comprehension-quiz/providers/anthropic.d.ts +34 -0
- package/dist/comprehension-quiz/providers/anthropic.js +53 -0
- package/dist/comprehension-quiz/providers/factory.d.ts +44 -0
- package/dist/comprehension-quiz/providers/factory.js +174 -0
- package/dist/comprehension-quiz/providers/interface.d.ts +59 -0
- package/dist/comprehension-quiz/providers/interface.js +63 -0
- package/dist/comprehension-quiz/providers/local.d.ts +34 -0
- package/dist/comprehension-quiz/providers/local.js +53 -0
- package/dist/comprehension-quiz/providers/ollama.d.ts +34 -0
- package/dist/comprehension-quiz/providers/ollama.js +53 -0
- package/dist/comprehension-quiz/providers/openai.d.ts +34 -0
- package/dist/comprehension-quiz/providers/openai.js +53 -0
- package/dist/comprehension-quiz/question-generator.d.ts +69 -0
- package/dist/comprehension-quiz/question-generator.js +141 -0
- package/dist/comprehension-quiz/quiz-executor.d.ts +70 -0
- package/dist/comprehension-quiz/quiz-executor.js +165 -0
- package/dist/comprehension-quiz/types.d.ts +115 -0
- package/dist/comprehension-quiz/types.js +11 -0
- package/dist/core/archive.d.ts +14 -1
- package/dist/core/archive.js +1350 -190
- package/dist/core/artifact-graph/graph.d.ts +28 -9
- package/dist/core/artifact-graph/graph.js +94 -43
- package/dist/core/artifact-graph/index.d.ts +1 -1
- package/dist/core/artifact-graph/index.js +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +30 -3
- package/dist/core/artifact-graph/instruction-loader.js +70 -12
- package/dist/core/artifact-graph/outputs.d.ts +1 -0
- package/dist/core/artifact-graph/outputs.js +67 -5
- package/dist/core/artifact-graph/resolver.d.ts +14 -0
- package/dist/core/artifact-graph/resolver.js +69 -15
- package/dist/core/artifact-graph/types.js +21 -3
- package/dist/core/available-tools.d.ts +2 -2
- package/dist/core/available-tools.js +14 -3
- package/dist/core/change-metadata/schema.d.ts +2 -0
- package/dist/core/change-metadata/schema.js +13 -0
- package/dist/core/change-status-policy.d.ts +10 -1
- package/dist/core/change-status-policy.js +30 -2
- package/dist/core/command-generation/adapters/amazon-q.d.ts +4 -0
- package/dist/core/command-generation/adapters/amazon-q.js +11 -5
- package/dist/core/command-generation/adapters/antigravity.js +6 -5
- package/dist/core/command-generation/adapters/auggie.js +7 -6
- package/dist/core/command-generation/adapters/bob.d.ts +5 -1
- package/dist/core/command-generation/adapters/bob.js +11 -10
- package/dist/core/command-generation/adapters/claude.d.ts +1 -1
- package/dist/core/command-generation/adapters/claude.js +12 -17
- package/dist/core/command-generation/adapters/cline.js +5 -5
- package/dist/core/command-generation/adapters/codebuddy.js +8 -7
- package/dist/core/command-generation/adapters/continue.js +8 -7
- package/dist/core/command-generation/adapters/costrict.js +7 -6
- package/dist/core/command-generation/adapters/crush.js +9 -9
- package/dist/core/command-generation/adapters/cursor.js +8 -8
- package/dist/core/command-generation/adapters/devin.d.ts +19 -0
- package/dist/core/command-generation/adapters/devin.js +36 -0
- package/dist/core/command-generation/adapters/factory.js +7 -6
- package/dist/core/command-generation/adapters/gemini.js +40 -5
- package/dist/core/command-generation/adapters/github-copilot.js +6 -5
- package/dist/core/command-generation/adapters/iflow.js +9 -8
- package/dist/core/command-generation/adapters/index.d.ts +4 -2
- package/dist/core/command-generation/adapters/index.js +4 -2
- package/dist/core/command-generation/adapters/junie.js +6 -5
- package/dist/core/command-generation/adapters/kilocode.js +1 -1
- package/dist/core/command-generation/adapters/kiro.js +6 -5
- package/dist/core/command-generation/adapters/lingma.js +9 -9
- package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
- package/dist/core/command-generation/adapters/oh-my-pi.js +42 -0
- package/dist/core/command-generation/adapters/opencode.js +6 -8
- package/dist/core/command-generation/adapters/pi.d.ts +2 -2
- package/dist/core/command-generation/adapters/pi.js +7 -10
- package/dist/core/command-generation/adapters/qoder.js +9 -9
- package/dist/core/command-generation/adapters/qwen.d.ts +7 -3
- package/dist/core/command-generation/adapters/qwen.js +14 -9
- package/dist/core/command-generation/adapters/roocode.d.ts +4 -4
- package/dist/core/command-generation/adapters/roocode.js +9 -9
- package/dist/core/command-generation/adapters/trae.d.ts +13 -0
- package/dist/core/command-generation/adapters/trae.js +28 -0
- package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
- package/dist/core/command-generation/adapters/zcode.js +33 -0
- package/dist/core/command-generation/generator.d.ts +8 -0
- package/dist/core/command-generation/generator.js +15 -1
- package/dist/core/command-generation/index.d.ts +1 -1
- package/dist/core/command-generation/index.js +1 -1
- package/dist/core/command-generation/invocation.d.ts +73 -0
- package/dist/core/command-generation/invocation.js +77 -0
- package/dist/core/command-generation/registry.js +8 -4
- package/dist/core/command-generation/types.d.ts +9 -2
- package/dist/core/command-generation/yaml.d.ts +20 -6
- package/dist/core/command-generation/yaml.js +31 -21
- package/dist/core/command-surface.d.ts +15 -0
- package/dist/core/command-surface.js +33 -0
- package/dist/core/completions/command-registry.js +16 -61
- package/dist/core/completions/generators/bash-generator.js +41 -41
- package/dist/core/completions/generators/fish-generator.js +7 -7
- package/dist/core/completions/generators/powershell-generator.js +44 -37
- package/dist/core/completions/generators/zsh-generator.js +33 -33
- package/dist/core/completions/installers/zsh-installer.d.ts +9 -0
- package/dist/core/completions/installers/zsh-installer.js +20 -4
- package/dist/core/completions/templates/bash-templates.js +24 -24
- package/dist/core/completions/templates/fish-templates.js +38 -38
- package/dist/core/completions/templates/powershell-templates.js +28 -28
- package/dist/core/completions/templates/zsh-templates.js +39 -39
- package/dist/core/config-prompts.d.ts +1 -1
- package/dist/core/config-prompts.js +14 -1
- package/dist/core/config-schema.d.ts +10 -1
- package/dist/core/config-schema.js +67 -1
- package/dist/core/config.d.ts +15 -0
- package/dist/core/config.js +46 -5
- package/dist/core/file-state.js +56 -24
- package/dist/core/github-copilot/cloud-agent.d.ts +95 -0
- package/dist/core/github-copilot/cloud-agent.js +497 -0
- package/dist/core/global-config.d.ts +16 -0
- package/dist/core/init.d.ts +58 -0
- package/dist/core/init.js +474 -73
- package/dist/core/legacy-cleanup.d.ts +59 -0
- package/dist/core/legacy-cleanup.js +253 -24
- package/dist/core/list.js +23 -16
- package/dist/core/migration.d.ts +73 -1
- package/dist/core/migration.js +360 -11
- package/dist/core/modify/conflict-detector.d.ts +26 -0
- package/dist/core/modify/conflict-detector.js +62 -0
- package/dist/core/modify/dependency-propagator.d.ts +38 -0
- package/dist/core/modify/dependency-propagator.js +103 -0
- package/dist/core/modify/history-tracker.d.ts +34 -0
- package/dist/core/modify/history-tracker.js +114 -0
- package/dist/core/modify/index.d.ts +7 -0
- package/dist/core/modify/index.js +7 -0
- package/dist/core/onboarding-commands.d.ts +30 -0
- package/dist/core/onboarding-commands.js +38 -0
- package/dist/core/openspec-root.js +18 -15
- package/dist/core/parsers/change-parser.d.ts +17 -2
- package/dist/core/parsers/change-parser.js +35 -21
- package/dist/core/parsers/code-fence.d.ts +15 -0
- package/dist/core/parsers/code-fence.js +49 -0
- package/dist/core/parsers/markdown-parser.d.ts +0 -3
- package/dist/core/parsers/markdown-parser.js +7 -60
- package/dist/core/parsers/requirement-blocks.d.ts +29 -0
- package/dist/core/parsers/requirement-blocks.js +150 -31
- package/dist/core/parsers/requirement-text.d.ts +48 -0
- package/dist/core/parsers/requirement-text.js +98 -0
- package/dist/core/parsers/spec-structure.d.ts +1 -1
- package/dist/core/parsers/spec-structure.js +20 -30
- package/dist/core/profile-sync-drift.d.ts +0 -8
- package/dist/core/profile-sync-drift.js +49 -50
- package/dist/core/profiles.d.ts +2 -2
- package/dist/core/profiles.js +2 -2
- package/dist/core/project-config.d.ts +27 -18
- package/dist/core/project-config.js +101 -34
- package/dist/core/references.js +42 -4
- package/dist/core/relationship-health.d.ts +8 -0
- package/dist/core/relationship-health.js +13 -0
- package/dist/core/root-selection.d.ts +9 -6
- package/dist/core/root-selection.js +42 -5
- package/dist/core/schemas/base.schema.js +7 -2
- package/dist/core/shared/allowed-tools.d.ts +12 -0
- package/dist/core/shared/allowed-tools.js +12 -0
- package/dist/core/shared/index.d.ts +1 -0
- package/dist/core/shared/index.js +1 -0
- package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
- package/dist/core/shared/skill-content-equivalence.js +41 -0
- package/dist/core/shared/skill-generation.js +17 -13
- package/dist/core/shared/skill-paths.d.ts +13 -0
- package/dist/core/shared/skill-paths.js +23 -0
- package/dist/core/shared/tool-detection.d.ts +27 -7
- package/dist/core/shared/tool-detection.js +167 -37
- package/dist/core/shared-skill-target.d.ts +16 -0
- package/dist/core/shared-skill-target.js +156 -0
- package/dist/core/specs-apply.d.ts +80 -33
- package/dist/core/specs-apply.js +667 -145
- package/dist/core/store/git.d.ts +13 -0
- package/dist/core/store/git.js +24 -0
- package/dist/core/store/operations.js +20 -0
- package/dist/core/templates/skill-templates.d.ts +3 -2
- package/dist/core/templates/skill-templates.js +3 -2
- package/dist/core/templates/workflows/apply-change.d.ts +10 -0
- package/dist/core/templates/workflows/apply-change.js +186 -323
- package/dist/core/templates/workflows/archive-change.js +387 -260
- package/dist/core/templates/workflows/bulk-archive-change.js +655 -475
- package/dist/core/templates/workflows/continue-change.js +210 -219
- package/dist/core/templates/workflows/explore.js +479 -443
- package/dist/core/templates/workflows/feedback.js +97 -97
- package/dist/core/templates/workflows/ff-change.js +200 -191
- package/dist/core/templates/workflows/modify-change.d.ts +4 -1
- package/dist/core/templates/workflows/modify-change.js +237 -115
- package/dist/core/templates/workflows/new-change.js +127 -134
- package/dist/core/templates/workflows/onboard.js +548 -542
- package/dist/core/templates/workflows/propose.js +273 -217
- package/dist/core/templates/workflows/store-selection.d.ts +1 -1
- package/dist/core/templates/workflows/store-selection.js +1 -1
- package/dist/core/templates/workflows/sync-specs.js +500 -273
- package/dist/core/templates/workflows/update-change.d.ts +10 -0
- package/dist/core/templates/workflows/update-change.js +177 -0
- package/dist/core/templates/workflows/verify-change.js +324 -326
- package/dist/core/update.d.ts +40 -5
- package/dist/core/update.js +411 -96
- package/dist/core/validation/constants.d.ts +4 -1
- package/dist/core/validation/constants.js +4 -1
- package/dist/core/validation/plan-validator.d.ts +8 -0
- package/dist/core/validation/plan-validator.js +45 -0
- package/dist/core/validation/task-numbering.d.ts +16 -0
- package/dist/core/validation/task-numbering.js +57 -0
- package/dist/core/validation/validator.d.ts +27 -3
- package/dist/core/validation/validator.js +337 -48
- package/dist/core/version-check.d.ts +152 -0
- package/dist/core/version-check.js +731 -0
- package/dist/core/view.js +13 -18
- package/dist/prompts/searchable-multi-select.js +1 -1
- package/dist/telemetry/caller-detection.d.ts +27 -0
- package/dist/telemetry/caller-detection.js +85 -0
- package/dist/telemetry/caller-detection.test.d.ts +5 -0
- package/dist/telemetry/config.d.ts +48 -2
- package/dist/telemetry/config.js +113 -11
- package/dist/telemetry/config.test.d.ts +5 -0
- package/dist/telemetry/deduplication.d.ts +35 -0
- package/dist/telemetry/deduplication.js +82 -0
- package/dist/telemetry/deduplication.test.d.ts +5 -0
- package/dist/telemetry/identity.d.ts +62 -22
- package/dist/telemetry/identity.js +147 -102
- package/dist/telemetry/index.d.ts +126 -15
- package/dist/telemetry/index.js +569 -37
- package/dist/telemetry/sanitization.d.ts +40 -0
- package/dist/telemetry/sanitization.js +114 -0
- package/dist/telemetry/sanitization.test.d.ts +5 -0
- package/dist/telemetry/throttling.d.ts +20 -0
- package/dist/telemetry/throttling.js +70 -0
- package/dist/ui/welcome-screen.d.ts +9 -1
- package/dist/ui/welcome-screen.js +91 -38
- package/dist/utils/change-metadata.d.ts +43 -0
- package/dist/utils/change-metadata.js +123 -7
- package/dist/utils/change-utils.d.ts +10 -4
- package/dist/utils/change-utils.js +20 -13
- package/dist/utils/ci.d.ts +12 -0
- package/dist/utils/ci.js +16 -0
- package/dist/utils/command-references.d.ts +92 -6
- package/dist/utils/command-references.js +180 -7
- package/dist/utils/date.d.ts +8 -0
- package/dist/utils/date.js +13 -0
- package/dist/utils/file-system.d.ts +10 -0
- package/dist/utils/file-system.js +70 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/interactive.d.ts +23 -0
- package/dist/utils/interactive.js +32 -0
- package/dist/utils/item-discovery.d.ts +19 -0
- package/dist/utils/item-discovery.js +30 -48
- package/dist/utils/shell-detection.d.ts +1 -1
- package/dist/utils/shell-detection.js +64 -11
- package/dist/utils/spec-discovery.d.ts +40 -0
- package/dist/utils/spec-discovery.js +115 -0
- package/dist/utils/task-progress.d.ts +29 -1
- package/dist/utils/task-progress.js +109 -15
- package/package.json +96 -84
- package/schemas/spec-driven/schema.yaml +252 -184
- package/schemas/spec-driven/templates/design.md +19 -19
- package/schemas/spec-driven/templates/plan-template.md +91 -0
- package/schemas/spec-driven/templates/proposal.md +29 -24
- package/schemas/spec-driven/templates/spec.md +11 -8
- package/schemas/spec-driven/templates/tasks.md +9 -9
- package/scripts/postinstall.js +83 -83
- package/dist/core/command-generation/adapters/codex.d.ts +0 -16
- package/dist/core/command-generation/adapters/codex.js +0 -39
- package/dist/core/command-generation/adapters/windsurf.d.ts +0 -14
- package/dist/core/command-generation/adapters/windsurf.js +0 -38
- package/dist/core/comprehension/config.d.ts +0 -20
- package/dist/core/comprehension/config.js +0 -23
- package/dist/core/comprehension/fingerprint.d.ts +0 -14
- package/dist/core/comprehension/fingerprint.js +0 -33
- package/dist/core/comprehension/index.d.ts +0 -57
- package/dist/core/comprehension/index.js +0 -87
- package/dist/core/comprehension/pass-record.d.ts +0 -29
- package/dist/core/comprehension/pass-record.js +0 -64
- package/dist/core/comprehension/stats.d.ts +0 -35
- package/dist/core/comprehension/stats.js +0 -133
- package/dist/core/templates/workflows/comprehension-guidance.d.ts +0 -9
- package/dist/core/templates/workflows/comprehension-guidance.js +0 -71
- package/dist/core/templates/workflows/mcp-guidance.d.ts +0 -13
- package/dist/core/templates/workflows/mcp-guidance.js +0 -131
- package/dist/core/templates/workflows/user-prompt-guidance.d.ts +0 -16
- package/dist/core/templates/workflows/user-prompt-guidance.js +0 -48
- package/dist/telemetry/caller.d.ts +0 -5
- package/dist/telemetry/caller.js +0 -29
- package/dist/telemetry/client.d.ts +0 -27
- package/dist/telemetry/client.js +0 -127
- package/dist/telemetry/command-context.d.ts +0 -13
- package/dist/telemetry/command-context.js +0 -59
- package/dist/telemetry/comprehension.d.ts +0 -44
- package/dist/telemetry/comprehension.js +0 -105
- package/dist/telemetry/content.d.ts +0 -10
- package/dist/telemetry/content.js +0 -56
- package/dist/telemetry/git-stats.d.ts +0 -12
- package/dist/telemetry/git-stats.js +0 -69
- package/dist/telemetry/identify-cache.d.ts +0 -7
- package/dist/telemetry/identify-cache.js +0 -47
- package/dist/telemetry/input.d.ts +0 -17
- package/dist/telemetry/input.js +0 -68
- package/dist/telemetry/marker.d.ts +0 -37
- package/dist/telemetry/marker.js +0 -67
- package/dist/telemetry/workflow.d.ts +0 -85
- package/dist/telemetry/workflow.js +0 -318
- package/schemas/spec-driven/templates/plan.md +0 -19
|
@@ -7,31 +7,89 @@
|
|
|
7
7
|
import ora from 'ora';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import * as fs from 'fs';
|
|
10
|
-
import { loadChangeContext, generateInstructions,
|
|
10
|
+
import { loadChangeContext, generateInstructions, resolveSchema, resolveArtifactOutputPath, resolveArtifactOutputs, } from '../../core/artifact-graph/index.js';
|
|
11
11
|
import { getChangeDir, resolveCurrentPlanningHomeSync, } from '../../core/planning-home.js';
|
|
12
|
-
import { resolveRootForCommand, withStoreFlag, toPlanningHome, toRootOutput,
|
|
12
|
+
import { resolveRootForCommand, withStoreFlag, toPlanningHome, toRootOutput, } from '../../core/root-selection.js';
|
|
13
13
|
import { assembleReferenceIndex, renderReferencedStoresBlock, renderReferencedStoresSection, } from '../../core/references.js';
|
|
14
14
|
import { readRegistrySnapshot } from '../../core/store/registry.js';
|
|
15
|
-
import { readProjectConfig } from '../../core/project-config.js';
|
|
16
|
-
import { checkComprehensionGate, ComprehensionPassError, computeSpecStats, recordComprehensionPass, resolveComprehensionConfig, } from '../../core/comprehension/index.js';
|
|
17
|
-
import { maybeEmitProposalReady, maybeEmitApplyReady, trackArtifactInstructions, trackArtifactContentChanges, trackArtifactModifyRequested, incrementComprehensionAttempt, incrementComprehensionFailureCount, trackComprehensionAttempt, trackComprehensionGateChecked, trackComprehensionRetakeRequired, } from '../../telemetry/index.js';
|
|
18
|
-
import { normalizeEditor, resolveWorkflowInputAsync, } from '../../telemetry/input.js';
|
|
15
|
+
import { loadOperationInputs, readProjectConfig, } from '../../core/project-config.js';
|
|
19
16
|
import { validateChangeExists, validateSchemaExists, } from './shared.js';
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
import { parseTaskLines } from '../../utils/task-progress.js';
|
|
18
|
+
import { fingerprintArtifacts, isPassRecordValid } from '../../comprehension-quiz/fingerprint.js';
|
|
19
|
+
// -----------------------------------------------------------------------------
|
|
20
|
+
// Comprehension Quiz Check
|
|
21
|
+
// -----------------------------------------------------------------------------
|
|
22
|
+
/**
|
|
23
|
+
* Check comprehension quiz status for a change
|
|
24
|
+
* @param changeName - Name of the change
|
|
25
|
+
* @param changeDir - Directory of the change
|
|
26
|
+
* @param contextFiles - Context files mapping
|
|
27
|
+
* @returns Comprehension status object
|
|
28
|
+
*/
|
|
29
|
+
async function checkComprehensionStatus(changeName, changeDir, contextFiles) {
|
|
30
|
+
// Get all artifact file paths
|
|
31
|
+
const artifactPaths = [];
|
|
32
|
+
for (const files of Object.values(contextFiles)) {
|
|
33
|
+
artifactPaths.push(...files);
|
|
34
|
+
}
|
|
35
|
+
// Generate current fingerprint
|
|
36
|
+
const currentFingerprint = await fingerprintArtifacts(artifactPaths);
|
|
37
|
+
// Check if pass record is valid
|
|
38
|
+
const passValid = await isPassRecordValid(changeName, currentFingerprint);
|
|
39
|
+
// Calculate question allocation based on spec-driven schema defaults
|
|
40
|
+
const totalSize = artifactPaths.reduce((sum, filePath) => {
|
|
41
|
+
try {
|
|
42
|
+
return sum + fs.statSync(filePath).size;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return sum;
|
|
46
|
+
}
|
|
47
|
+
}, 0);
|
|
48
|
+
const questionCount = totalSize < 2048 ? 5 : totalSize < 10240 ? 7 : 10;
|
|
49
|
+
const questionAllocation = {
|
|
50
|
+
plan: Math.round(questionCount * 0.5),
|
|
51
|
+
specs: Math.round(questionCount * 0.2),
|
|
52
|
+
design: Math.round(questionCount * 0.1),
|
|
53
|
+
proposal: Math.round(questionCount * 0.1),
|
|
54
|
+
tasks: Math.round(questionCount * 0.1),
|
|
27
55
|
};
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
56
|
+
// Count requirements and scenarios from specs
|
|
57
|
+
const specFiles = contextFiles.specs || [];
|
|
58
|
+
let requirementCount = 0;
|
|
59
|
+
let scenarioCount = 0;
|
|
60
|
+
for (const specFile of specFiles) {
|
|
61
|
+
try {
|
|
62
|
+
const content = await fs.promises.readFile(specFile, 'utf-8');
|
|
63
|
+
requirementCount += (content.match(/### Requirement:/g) || []).length;
|
|
64
|
+
scenarioCount += (content.match(/#### Scenario:/g) || []).length;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Skip files that can't be read
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Count pending tasks
|
|
71
|
+
const tasksFile = contextFiles.tasks?.[0];
|
|
72
|
+
let pendingTaskCount = 0;
|
|
73
|
+
if (tasksFile) {
|
|
74
|
+
try {
|
|
75
|
+
const content = await fs.promises.readFile(tasksFile, 'utf-8');
|
|
76
|
+
pendingTaskCount = (content.match(/- \[ \]/g) || []).length;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Skip if can't read
|
|
80
|
+
}
|
|
32
81
|
}
|
|
33
|
-
|
|
34
|
-
|
|
82
|
+
return {
|
|
83
|
+
required: true,
|
|
84
|
+
passed: passValid,
|
|
85
|
+
thresholdPercent: 80,
|
|
86
|
+
questionCount,
|
|
87
|
+
questionAllocation,
|
|
88
|
+
optionsPerQuestion: 3,
|
|
89
|
+
requirementCount,
|
|
90
|
+
scenarioCount,
|
|
91
|
+
pendingTaskCount,
|
|
92
|
+
};
|
|
35
93
|
}
|
|
36
94
|
// -----------------------------------------------------------------------------
|
|
37
95
|
// Artifact Instructions Command
|
|
@@ -74,10 +132,12 @@ export async function instructionsCommand(artifactId, options) {
|
|
|
74
132
|
if (options.schema) {
|
|
75
133
|
validateSchemaExists(options.schema, projectRoot);
|
|
76
134
|
}
|
|
135
|
+
const { projectConfig, references } = await loadRootConfigContext(root);
|
|
77
136
|
// loadChangeContext will auto-detect schema from metadata if not provided
|
|
78
137
|
const context = loadChangeContext(projectRoot, changeName, options.schema, {
|
|
79
138
|
changeDir: getChangeDir(planningHome, changeName),
|
|
80
139
|
planningHome,
|
|
140
|
+
projectConfig,
|
|
81
141
|
});
|
|
82
142
|
if (!artifactId) {
|
|
83
143
|
spinner?.stop();
|
|
@@ -90,32 +150,11 @@ export async function instructionsCommand(artifactId, options) {
|
|
|
90
150
|
const validIds = context.graph.getAllArtifacts().map((a) => a.id);
|
|
91
151
|
throw new Error(`Artifact '${artifactId}' not found in schema '${context.schemaName}'. Valid artifacts:\n ${validIds.join('\n ')}`);
|
|
92
152
|
}
|
|
93
|
-
const { projectConfig, references } = await loadRootConfigContext(root);
|
|
94
153
|
const instructions = generateInstructions(context, artifactId, projectRoot, {
|
|
95
154
|
projectConfig,
|
|
96
155
|
references,
|
|
97
156
|
});
|
|
98
157
|
const isBlocked = instructions.dependencies.some((d) => !d.done);
|
|
99
|
-
const artifactOutputs = resolveArtifactOutputs(context.changeDir, artifact.generates);
|
|
100
|
-
await trackArtifactInstructions({
|
|
101
|
-
changeDir: context.changeDir,
|
|
102
|
-
changeName,
|
|
103
|
-
artifactId,
|
|
104
|
-
artifactWasDone: artifactOutputs.length > 0,
|
|
105
|
-
artifactPaths: artifactOutputs,
|
|
106
|
-
});
|
|
107
|
-
const contextFiles = {};
|
|
108
|
-
for (const a of context.graph.getAllArtifacts()) {
|
|
109
|
-
const outputs = resolveArtifactOutputs(context.changeDir, a.generates);
|
|
110
|
-
if (outputs.length > 0) {
|
|
111
|
-
contextFiles[a.id] = outputs;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
await trackArtifactContentChanges({
|
|
115
|
-
changeDir: context.changeDir,
|
|
116
|
-
changeName,
|
|
117
|
-
contextFiles,
|
|
118
|
-
});
|
|
119
158
|
spinner?.stop();
|
|
120
159
|
if (options.json) {
|
|
121
160
|
console.log(JSON.stringify({ ...instructions, root: toRootOutput(root) }, null, 2));
|
|
@@ -133,6 +172,17 @@ export function printInstructionsText(instructions, isBlocked) {
|
|
|
133
172
|
// Opening tag
|
|
134
173
|
console.log(`<artifact id="${artifactId}" change="${changeName}" schema="${schemaName}">`);
|
|
135
174
|
console.log();
|
|
175
|
+
// Artifacts skipped via skip_specs get no creation directive: emitting the
|
|
176
|
+
// task/template anyway would prompt an agent to write spec files that
|
|
177
|
+
// validate then rejects as conflicting with the marker.
|
|
178
|
+
if (instructions.skipped) {
|
|
179
|
+
console.log('<warning>');
|
|
180
|
+
console.log(instructions.warning ?? 'This artifact is skipped (skip_specs is set in .openspec.yaml).');
|
|
181
|
+
console.log('</warning>');
|
|
182
|
+
console.log();
|
|
183
|
+
console.log('</artifact>');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
136
186
|
// Warning for blocked artifacts
|
|
137
187
|
if (isBlocked) {
|
|
138
188
|
const missing = dependencies.filter((d) => !d.done).map((d) => d.id);
|
|
@@ -174,9 +224,18 @@ export function printInstructionsText(instructions, isBlocked) {
|
|
|
174
224
|
// Dependencies (files to read for context)
|
|
175
225
|
if (dependencies.length > 0) {
|
|
176
226
|
console.log('<dependencies>');
|
|
177
|
-
console.log('Read these files
|
|
227
|
+
console.log('Read the current contents of these files before creating this artifact (re-read them from disk even if you saw them earlier - they may have been edited):');
|
|
178
228
|
console.log();
|
|
179
229
|
for (const dep of dependencies) {
|
|
230
|
+
// A dependency satisfied via skip_specs has no files by design: telling
|
|
231
|
+
// the agent to read them (or calling them "done") would send it hunting
|
|
232
|
+
// for spec files that must not exist.
|
|
233
|
+
if (dep.skipped) {
|
|
234
|
+
console.log(`<dependency id="${dep.id}" status="skipped">`);
|
|
235
|
+
console.log(` <description>Skipped: the change declares skip_specs, so this artifact has no files to read.</description>`);
|
|
236
|
+
console.log('</dependency>');
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
180
239
|
const status = dep.done ? 'done' : 'missing';
|
|
181
240
|
const fullPath = path.join(changeDir, dep.path);
|
|
182
241
|
console.log(`<dependency id="${dep.id}" status="${status}">`);
|
|
@@ -224,49 +283,29 @@ export function printInstructionsText(instructions, isBlocked) {
|
|
|
224
283
|
// Apply Instructions Command
|
|
225
284
|
// -----------------------------------------------------------------------------
|
|
226
285
|
/**
|
|
227
|
-
*
|
|
286
|
+
* Turns parsed task lines into the listed task items.
|
|
287
|
+
*
|
|
288
|
+
* A checkbox with no text after it is left out of the list: this is work for an
|
|
289
|
+
* agent to act on and tick off, and a bare `- [ ]` gives it nothing to match.
|
|
290
|
+
* It still counts toward progress, which is taken from every parsed line, so
|
|
291
|
+
* this list can be shorter than the totals beside it but never disagrees with
|
|
292
|
+
* `openspec list` or archive about how much work is left. An empty list is also
|
|
293
|
+
* what puts apply in its "nothing to work on" state, so a file of nothing but
|
|
294
|
+
* text-less checkboxes asks to be rewritten instead of being called done.
|
|
228
295
|
*/
|
|
229
|
-
function
|
|
296
|
+
function toTaskItems(parsed) {
|
|
230
297
|
const tasks = [];
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const description = checkboxMatch[2].trim();
|
|
240
|
-
tasks.push({
|
|
241
|
-
id: `${taskIndex}`,
|
|
242
|
-
description,
|
|
243
|
-
done,
|
|
244
|
-
});
|
|
245
|
-
}
|
|
298
|
+
for (const task of parsed) {
|
|
299
|
+
if (task.description.length === 0)
|
|
300
|
+
continue;
|
|
301
|
+
tasks.push({
|
|
302
|
+
id: `${tasks.length + 1}`,
|
|
303
|
+
description: task.description,
|
|
304
|
+
done: task.done,
|
|
305
|
+
});
|
|
246
306
|
}
|
|
247
307
|
return tasks;
|
|
248
308
|
}
|
|
249
|
-
function resolveComprehensionQuestionCount(optionCount, specPaths, projectConfig, pendingTaskCount, artifactPresence) {
|
|
250
|
-
if (optionCount !== undefined && optionCount > 0) {
|
|
251
|
-
return optionCount;
|
|
252
|
-
}
|
|
253
|
-
const config = resolveComprehensionConfig(projectConfig);
|
|
254
|
-
return computeSpecStats(specPaths, config, pendingTaskCount, artifactPresence).questionCount;
|
|
255
|
-
}
|
|
256
|
-
async function emitComprehensionAttemptAfterPass(params) {
|
|
257
|
-
await trackComprehensionAttempt({
|
|
258
|
-
changeDir: params.changeDir,
|
|
259
|
-
changeName: params.changeName,
|
|
260
|
-
attempt: params.attempt,
|
|
261
|
-
scorePercent: params.scorePercent,
|
|
262
|
-
thresholdPercent: params.thresholdPercent,
|
|
263
|
-
questionCount: params.questionCount,
|
|
264
|
-
passed: true,
|
|
265
|
-
failureCountBefore: params.failureCountBefore,
|
|
266
|
-
nextMilestone: params.applyReadyEmitted ? 'apply_ready' : undefined,
|
|
267
|
-
contextFiles: params.contextFiles,
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
309
|
/**
|
|
271
310
|
* Generates apply instructions for implementing tasks from a change.
|
|
272
311
|
* Schema-aware: reads apply phase configuration from schema to determine
|
|
@@ -279,6 +318,7 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
279
318
|
const context = loadChangeContext(projectRoot, changeName, schemaName, {
|
|
280
319
|
changeDir: getChangeDir(planningHome, changeName),
|
|
281
320
|
planningHome,
|
|
321
|
+
projectConfig: options.projectConfig,
|
|
282
322
|
});
|
|
283
323
|
const changeDir = context.changeDir;
|
|
284
324
|
// Get the full schema to access the apply phase configuration
|
|
@@ -289,9 +329,15 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
289
329
|
const requiredArtifactIds = applyConfig?.requires ?? schema.artifacts.map((a) => a.id);
|
|
290
330
|
const tracksFile = applyConfig?.tracks ?? null;
|
|
291
331
|
const schemaInstruction = applyConfig?.instruction ?? null;
|
|
292
|
-
|
|
332
|
+
const operationInputs = loadOperationInputs(options.projectConfig ?? null, 'apply');
|
|
333
|
+
// Check which required artifacts are missing. Artifacts the change skips
|
|
334
|
+
// via skip_specs count as present - their files must not exist, and
|
|
335
|
+
// status already reports them complete, so apply cannot block on them.
|
|
293
336
|
const missingArtifacts = [];
|
|
294
337
|
for (const artifactId of requiredArtifactIds) {
|
|
338
|
+
if (context.skippedArtifacts?.has(artifactId)) {
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
295
341
|
const artifact = schema.artifacts.find((a) => a.id === artifactId);
|
|
296
342
|
if (artifact && resolveArtifactOutputs(changeDir, artifact.generates).length === 0) {
|
|
297
343
|
missingArtifacts.push(artifactId);
|
|
@@ -306,38 +352,47 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
306
352
|
}
|
|
307
353
|
}
|
|
308
354
|
// Parse tasks if tracking file exists
|
|
309
|
-
let
|
|
355
|
+
let parsedTasks = [];
|
|
310
356
|
let tracksFileExists = false;
|
|
311
357
|
if (tracksFile) {
|
|
312
|
-
const tracksPath =
|
|
358
|
+
const tracksPath = resolveArtifactOutputPath(changeDir, tracksFile);
|
|
313
359
|
tracksFileExists = fs.existsSync(tracksPath);
|
|
314
360
|
if (tracksFileExists) {
|
|
315
361
|
const tasksContent = await fs.promises.readFile(tracksPath, 'utf-8');
|
|
316
|
-
|
|
362
|
+
parsedTasks = parseTaskLines(tasksContent);
|
|
317
363
|
}
|
|
318
364
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
365
|
+
const tasks = toTaskItems(parsedTasks);
|
|
366
|
+
// Calculate progress over every checkbox in the file, listed or not, so these
|
|
367
|
+
// numbers match `openspec list` and archive's incomplete-task check.
|
|
368
|
+
const total = parsedTasks.length;
|
|
369
|
+
const complete = parsedTasks.filter((task) => task.done).length;
|
|
322
370
|
const remaining = total - complete;
|
|
323
371
|
// Determine state and instruction
|
|
324
372
|
let state;
|
|
325
373
|
let instruction;
|
|
374
|
+
// Check comprehension quiz status first
|
|
375
|
+
const comprehensionStatus = await checkComprehensionStatus(changeName, changeDir, contextFiles);
|
|
326
376
|
if (missingArtifacts.length > 0) {
|
|
327
377
|
state = 'blocked';
|
|
328
378
|
instruction = `Cannot apply this change yet. Missing artifacts: ${missingArtifacts.join(', ')}.\nUse the openspec-continue-change skill to create the missing artifacts first.`;
|
|
329
379
|
}
|
|
380
|
+
else if (comprehensionStatus.required && !comprehensionStatus.passed) {
|
|
381
|
+
state = 'comprehension_blocked';
|
|
382
|
+
instruction = 'Complete the comprehension quiz in /opsx:apply before implementation (score ≥ 80% on proposal, design, specs, plan, and tasks; plan receives the majority of questions per questionAllocation).';
|
|
383
|
+
}
|
|
330
384
|
else if (tracksFile && !tracksFileExists) {
|
|
331
385
|
// Tracking file configured but doesn't exist yet
|
|
332
386
|
const tracksFilename = path.basename(tracksFile);
|
|
333
387
|
state = 'blocked';
|
|
334
388
|
instruction = `The ${tracksFilename} file is missing and must be created.\nUse openspec-continue-change to generate the tracking file.`;
|
|
335
389
|
}
|
|
336
|
-
else if (tracksFile && tracksFileExists &&
|
|
337
|
-
// Tracking file exists but
|
|
390
|
+
else if (tracksFile && tracksFileExists && tasks.length === 0) {
|
|
391
|
+
// Tracking file exists but lists nothing an agent can work on: either no
|
|
392
|
+
// checkboxes at all, or only checkboxes with no text after them.
|
|
338
393
|
const tracksFilename = path.basename(tracksFile);
|
|
339
394
|
state = 'blocked';
|
|
340
|
-
instruction = `The ${tracksFilename} file exists but contains no tasks.\nAdd tasks to ${tracksFilename} or regenerate it with openspec-continue-change.`;
|
|
395
|
+
instruction = `The ${tracksFilename} file exists but contains no tasks to work on.\nAdd tasks to ${tracksFilename} or regenerate it with openspec-continue-change.`;
|
|
341
396
|
}
|
|
342
397
|
else if (tracksFile && remaining === 0 && total > 0) {
|
|
343
398
|
state = 'all_done';
|
|
@@ -352,58 +407,6 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
352
407
|
state = 'ready';
|
|
353
408
|
instruction = schemaInstruction?.trim() ?? 'Read context files, work through pending tasks, mark complete as you go.\nPause if you hit blockers or need clarification.';
|
|
354
409
|
}
|
|
355
|
-
let missingComprehension;
|
|
356
|
-
let comprehension;
|
|
357
|
-
let applyReadyEmitted = false;
|
|
358
|
-
await trackArtifactContentChanges({ changeDir, changeName, contextFiles });
|
|
359
|
-
await maybeEmitProposalReady({
|
|
360
|
-
changeDir,
|
|
361
|
-
changeName,
|
|
362
|
-
schema: context.schemaName,
|
|
363
|
-
missingArtifacts,
|
|
364
|
-
artifactCount: schema.artifacts.length,
|
|
365
|
-
contextFiles,
|
|
366
|
-
});
|
|
367
|
-
if (state === 'ready') {
|
|
368
|
-
const specPaths = contextFiles.specs ?? [];
|
|
369
|
-
const tasksPath = tracksFile && tracksFileExists ? path.join(changeDir, tracksFile) : null;
|
|
370
|
-
const planPath = resolvePlanPath(contextFiles, changeDir);
|
|
371
|
-
const pendingTaskCount = tasks.filter((task) => !task.done).length;
|
|
372
|
-
const artifactPresence = buildArtifactPresence(contextFiles, pendingTaskCount);
|
|
373
|
-
const gate = checkComprehensionGate(changeDir, specPaths, options.projectConfig ?? readProjectConfig(projectRoot), { tasksPath, planPath, pendingTaskCount, artifactPresence });
|
|
374
|
-
if (gate.active && !gate.passed && gate.info) {
|
|
375
|
-
state = 'blocked';
|
|
376
|
-
missingComprehension = true;
|
|
377
|
-
comprehension = gate.info;
|
|
378
|
-
instruction = `Complete the comprehension quiz in /opsx:apply before implementation (score ≥ ${gate.info.thresholdPercent}% on proposal, design, specs, plan, and tasks; plan receives the majority of questions per questionAllocation).`;
|
|
379
|
-
}
|
|
380
|
-
else if (gate.active && gate.info) {
|
|
381
|
-
comprehension = gate.info;
|
|
382
|
-
}
|
|
383
|
-
applyReadyEmitted = await maybeEmitApplyReady({
|
|
384
|
-
changeDir,
|
|
385
|
-
changeName,
|
|
386
|
-
state,
|
|
387
|
-
contextFiles,
|
|
388
|
-
});
|
|
389
|
-
if (gate.active && gate.info) {
|
|
390
|
-
await trackComprehensionGateChecked({
|
|
391
|
-
changeDir,
|
|
392
|
-
changeName,
|
|
393
|
-
passed: gate.passed,
|
|
394
|
-
gateInfo: gate.info,
|
|
395
|
-
state: gate.passed ? 'ready' : 'blocked',
|
|
396
|
-
contextFiles,
|
|
397
|
-
});
|
|
398
|
-
if (!gate.passed && gate.info.bestScorePercent !== undefined) {
|
|
399
|
-
await trackComprehensionRetakeRequired({
|
|
400
|
-
changeDir,
|
|
401
|
-
changeName,
|
|
402
|
-
gateInfo: gate.info,
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
410
|
return {
|
|
408
411
|
changeName,
|
|
409
412
|
changeDir,
|
|
@@ -413,11 +416,11 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
413
416
|
tasks,
|
|
414
417
|
state,
|
|
415
418
|
missingArtifacts: missingArtifacts.length > 0 ? missingArtifacts : undefined,
|
|
416
|
-
missingComprehension,
|
|
417
|
-
comprehension,
|
|
418
419
|
instruction,
|
|
419
|
-
|
|
420
|
+
comprehension: comprehensionStatus,
|
|
421
|
+
applyReadyEmitted: comprehensionStatus.passed,
|
|
420
422
|
...(references !== undefined ? { references } : {}),
|
|
423
|
+
...operationInputs,
|
|
421
424
|
};
|
|
422
425
|
}
|
|
423
426
|
export async function applyInstructionsCommand(options) {
|
|
@@ -435,119 +438,9 @@ export async function applyInstructionsCommand(options) {
|
|
|
435
438
|
if (options.schema) {
|
|
436
439
|
validateSchemaExists(options.schema, projectRoot);
|
|
437
440
|
}
|
|
441
|
+
// One parsed config snapshot supplies schema fallback, references, context,
|
|
442
|
+
// and operation guidance for this command.
|
|
438
443
|
const { projectConfig, references } = await loadRootConfigContext(root);
|
|
439
|
-
if (options.recordComprehensionPass) {
|
|
440
|
-
if (options.score === undefined) {
|
|
441
|
-
spinner?.stop();
|
|
442
|
-
throw new Error('--score is required with --record-comprehension-pass');
|
|
443
|
-
}
|
|
444
|
-
const context = loadChangeContext(projectRoot, changeName, options.schema, {
|
|
445
|
-
changeDir: getChangeDir(planningHome, changeName),
|
|
446
|
-
planningHome,
|
|
447
|
-
});
|
|
448
|
-
const changeDir = context.changeDir;
|
|
449
|
-
const schema = resolveSchema(context.schemaName, projectRoot);
|
|
450
|
-
const specPaths = [];
|
|
451
|
-
const specsArtifact = schema.artifacts.find((a) => a.id === 'specs');
|
|
452
|
-
if (specsArtifact) {
|
|
453
|
-
specPaths.push(...resolveArtifactOutputs(changeDir, specsArtifact.generates));
|
|
454
|
-
}
|
|
455
|
-
const tracksFile = schema.apply?.tracks ?? null;
|
|
456
|
-
const tasksPath = tracksFile ? path.join(changeDir, tracksFile) : null;
|
|
457
|
-
const planPath = fs.existsSync(path.join(changeDir, 'plan.md'))
|
|
458
|
-
? path.join(changeDir, 'plan.md')
|
|
459
|
-
: null;
|
|
460
|
-
const pendingTaskCount = tasksPath && fs.existsSync(tasksPath)
|
|
461
|
-
? parseTasksFile(fs.readFileSync(tasksPath, 'utf-8')).filter((t) => !t.done).length
|
|
462
|
-
: 0;
|
|
463
|
-
const contextFilesForPresence = {};
|
|
464
|
-
for (const artifact of schema.artifacts) {
|
|
465
|
-
const outputs = resolveArtifactOutputs(changeDir, artifact.generates);
|
|
466
|
-
if (outputs.length > 0) {
|
|
467
|
-
contextFilesForPresence[artifact.id] = outputs;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
const artifactPresence = buildArtifactPresence(contextFilesForPresence, pendingTaskCount);
|
|
471
|
-
const questionCount = resolveComprehensionQuestionCount(options.questionCount, specPaths, projectConfig, pendingTaskCount, artifactPresence);
|
|
472
|
-
const { attempt, failureCountBefore } = await incrementComprehensionAttempt(changeDir);
|
|
473
|
-
try {
|
|
474
|
-
const record = recordComprehensionPass({
|
|
475
|
-
changeDir,
|
|
476
|
-
specPaths,
|
|
477
|
-
tasksPath,
|
|
478
|
-
planPath,
|
|
479
|
-
projectConfig,
|
|
480
|
-
scorePercent: options.score,
|
|
481
|
-
attempt,
|
|
482
|
-
questionCount,
|
|
483
|
-
pendingTaskCount,
|
|
484
|
-
artifactPresence,
|
|
485
|
-
});
|
|
486
|
-
spinner?.stop();
|
|
487
|
-
const instructions = await generateApplyInstructions(projectRoot, changeName, options.schema, {
|
|
488
|
-
planningHome,
|
|
489
|
-
references,
|
|
490
|
-
projectConfig,
|
|
491
|
-
});
|
|
492
|
-
await emitComprehensionAttemptAfterPass({
|
|
493
|
-
changeDir,
|
|
494
|
-
changeName,
|
|
495
|
-
attempt: record.attempt,
|
|
496
|
-
failureCountBefore,
|
|
497
|
-
scorePercent: record.score_percent,
|
|
498
|
-
thresholdPercent: record.threshold_percent,
|
|
499
|
-
questionCount: record.question_count,
|
|
500
|
-
contextFiles: instructions.contextFiles,
|
|
501
|
-
applyReadyEmitted: instructions.applyReadyEmitted ?? false,
|
|
502
|
-
});
|
|
503
|
-
if (options.json) {
|
|
504
|
-
console.log(JSON.stringify({
|
|
505
|
-
recorded: true,
|
|
506
|
-
comprehensionPass: record,
|
|
507
|
-
...instructions,
|
|
508
|
-
root: toRootOutput(root),
|
|
509
|
-
}, null, 2));
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
512
|
-
console.log(`Comprehension pass recorded (${record.score_percent}%).`);
|
|
513
|
-
printApplyInstructionsText(instructions);
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
catch (error) {
|
|
517
|
-
spinner?.stop();
|
|
518
|
-
if (error instanceof ComprehensionPassError) {
|
|
519
|
-
await incrementComprehensionFailureCount(changeDir);
|
|
520
|
-
await trackComprehensionAttempt({
|
|
521
|
-
changeDir,
|
|
522
|
-
changeName,
|
|
523
|
-
attempt,
|
|
524
|
-
scorePercent: options.score,
|
|
525
|
-
thresholdPercent: error.threshold,
|
|
526
|
-
questionCount,
|
|
527
|
-
passed: false,
|
|
528
|
-
failureCountBefore,
|
|
529
|
-
contextFiles: contextFilesForPresence,
|
|
530
|
-
});
|
|
531
|
-
if (options.json) {
|
|
532
|
-
console.log(JSON.stringify({
|
|
533
|
-
recorded: false,
|
|
534
|
-
status: [
|
|
535
|
-
{
|
|
536
|
-
severity: 'error',
|
|
537
|
-
code: 'comprehension_score_below_threshold',
|
|
538
|
-
message: error.message,
|
|
539
|
-
},
|
|
540
|
-
],
|
|
541
|
-
root: toRootOutput(root),
|
|
542
|
-
}, null, 2));
|
|
543
|
-
process.exitCode = 1;
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
throw error;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
// generateApplyInstructions uses loadChangeContext which auto-detects schema
|
|
551
444
|
const instructions = await generateApplyInstructions(projectRoot, changeName, options.schema, {
|
|
552
445
|
planningHome,
|
|
553
446
|
references,
|
|
@@ -566,7 +459,7 @@ export async function applyInstructionsCommand(options) {
|
|
|
566
459
|
}
|
|
567
460
|
}
|
|
568
461
|
export function printApplyInstructionsText(instructions) {
|
|
569
|
-
const { changeName, schemaName, contextFiles, progress, tasks, state, missingArtifacts,
|
|
462
|
+
const { changeName, schemaName, contextFiles, progress, tasks, state, missingArtifacts, instruction } = instructions;
|
|
570
463
|
console.log(`## Apply: ${changeName}`);
|
|
571
464
|
console.log(`Schema: ${schemaName}`);
|
|
572
465
|
console.log();
|
|
@@ -582,24 +475,6 @@ export function printApplyInstructionsText(instructions) {
|
|
|
582
475
|
console.log('Use the openspec-continue-change skill to create these first.');
|
|
583
476
|
console.log();
|
|
584
477
|
}
|
|
585
|
-
if (state === 'blocked' && missingComprehension && comprehension) {
|
|
586
|
-
console.log('### ⚠️ Comprehension Required');
|
|
587
|
-
console.log();
|
|
588
|
-
console.log(`Pass the comprehension quiz (score ≥ ${comprehension.thresholdPercent}%) before implementation.`);
|
|
589
|
-
console.log(`Questions: ${comprehension.questionCount} (${comprehension.optionsPerQuestion} options each)`);
|
|
590
|
-
const allocationParts = Object.entries(comprehension.questionAllocation)
|
|
591
|
-
.filter(([, count]) => (count ?? 0) > 0)
|
|
592
|
-
.map(([category, count]) => `${category}×${count}`);
|
|
593
|
-
if (allocationParts.length > 0) {
|
|
594
|
-
console.log(`Allocation: ${allocationParts.join(', ')}`);
|
|
595
|
-
}
|
|
596
|
-
console.log(`Specs: ${comprehension.requirementCount} requirements, ${comprehension.scenarioCount} scenarios; Tasks: ${comprehension.pendingTaskCount} pending`);
|
|
597
|
-
if (comprehension.bestScorePercent !== undefined) {
|
|
598
|
-
console.log(`Previous score: ${comprehension.bestScorePercent}% (artifacts changed — retake required)`);
|
|
599
|
-
}
|
|
600
|
-
console.log('Complete the quiz via /opsx:apply.');
|
|
601
|
-
console.log();
|
|
602
|
-
}
|
|
603
478
|
// Context files (dynamically from schema)
|
|
604
479
|
const contextFileEntries = Object.entries(contextFiles);
|
|
605
480
|
if (contextFileEntries.length > 0) {
|
|
@@ -634,125 +509,57 @@ export function printApplyInstructionsText(instructions) {
|
|
|
634
509
|
// Instruction
|
|
635
510
|
console.log('### Instruction');
|
|
636
511
|
console.log(instruction);
|
|
512
|
+
console.log();
|
|
513
|
+
printOperationInputsText(instructions);
|
|
637
514
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
? `Downstream artifacts to refresh: ${downstreamArtifacts.join(', ')}.`
|
|
644
|
-
: 'No downstream artifacts depend on this source.';
|
|
645
|
-
const requestLine = modifyInput
|
|
646
|
-
? `User modify request: ${modifyInput}`
|
|
647
|
-
: 'Apply the user modify request from the conversation.';
|
|
648
|
-
return [
|
|
649
|
-
`Revise the "${sourceArtifact}" artifact for this change (pre-apply only).`,
|
|
650
|
-
requestLine,
|
|
651
|
-
downstreamLine,
|
|
652
|
-
'For each artifact in artifactsToUpdate (in order): run openspec instructions <id> --change <name> --json, read upstream dependencies, apply a surgical edit aligned with the modify request, and write to resolvedOutputPath.',
|
|
653
|
-
'When proposal capabilities change, add/remove/rename specs under specs/ accordingly.',
|
|
654
|
-
'After all updates, run openspec status --change <name> --json, then hand off to /opsx:apply.',
|
|
655
|
-
].join('\n');
|
|
515
|
+
export function generateArchiveInstructions(changeName, projectConfig) {
|
|
516
|
+
return {
|
|
517
|
+
changeName,
|
|
518
|
+
...loadOperationInputs(projectConfig, 'archive'),
|
|
519
|
+
};
|
|
656
520
|
}
|
|
657
|
-
export async function
|
|
521
|
+
export async function archiveInstructionsCommand(options) {
|
|
658
522
|
const root = await resolveRootForCommand(options, { json: options.json });
|
|
659
523
|
if (!root) {
|
|
660
524
|
return;
|
|
661
525
|
}
|
|
662
|
-
const spinner = options.json ? undefined : ora('
|
|
526
|
+
const spinner = options.json ? undefined : ora('Loading archive inputs...').start();
|
|
663
527
|
try {
|
|
664
|
-
const
|
|
665
|
-
const
|
|
666
|
-
const
|
|
667
|
-
if (options.schema) {
|
|
668
|
-
validateSchemaExists(options.schema, projectRoot);
|
|
669
|
-
}
|
|
670
|
-
const sourceArtifact = options.artifact?.trim();
|
|
671
|
-
if (!sourceArtifact) {
|
|
672
|
-
spinner?.stop();
|
|
673
|
-
throw new Error('Missing required option --artifact. Specify the artifact to modify (e.g., design, proposal, tasks).');
|
|
674
|
-
}
|
|
675
|
-
const modifyInput = await resolveWorkflowInputAsync({
|
|
676
|
-
workflowInput: options.workflowInput,
|
|
677
|
-
workflowInputFile: options.workflowInputFile,
|
|
678
|
-
});
|
|
679
|
-
const editor = normalizeEditor(options.editor);
|
|
680
|
-
const context = loadChangeContext(projectRoot, changeName, options.schema, {
|
|
681
|
-
changeDir: getChangeDir(planningHome, changeName),
|
|
682
|
-
planningHome,
|
|
683
|
-
});
|
|
684
|
-
const artifact = context.graph.getArtifact(sourceArtifact);
|
|
685
|
-
if (!artifact) {
|
|
686
|
-
spinner?.stop();
|
|
687
|
-
const validIds = context.graph.getAllArtifacts().map((a) => a.id);
|
|
688
|
-
throw new Error(`Artifact '${sourceArtifact}' not found in schema '${context.schemaName}'. Valid artifacts:\n ${validIds.join('\n ')}`);
|
|
689
|
-
}
|
|
690
|
-
const applyInstructions = await generateApplyInstructions(projectRoot, changeName, options.schema, {
|
|
691
|
-
planningHome,
|
|
692
|
-
});
|
|
693
|
-
if (applyInstructions.progress.complete > 0) {
|
|
694
|
-
spinner?.stop();
|
|
695
|
-
throw new Error(`Apply has already started (${applyInstructions.progress.complete}/${applyInstructions.progress.total} tasks complete). /opsx:modify is pre-apply only. Edit artifacts manually or start a new change.`);
|
|
696
|
-
}
|
|
697
|
-
if (applyInstructions.missingArtifacts && applyInstructions.missingArtifacts.length > 0) {
|
|
698
|
-
spinner?.stop();
|
|
699
|
-
throw new Error(`Change is not ready to modify. Missing artifacts: ${applyInstructions.missingArtifacts.join(', ')}. Use /opsx:continue or /opsx:propose to create them first.`);
|
|
700
|
-
}
|
|
701
|
-
const sourceOutputs = resolveArtifactOutputs(context.changeDir, artifact.generates);
|
|
702
|
-
if (sourceOutputs.length === 0) {
|
|
703
|
-
spinner?.stop();
|
|
704
|
-
throw new Error(`Artifact '${sourceArtifact}' does not exist yet. Use /opsx:continue to create it first.`);
|
|
705
|
-
}
|
|
706
|
-
const downstreamArtifacts = context.graph.getTransitiveDependents(sourceArtifact);
|
|
707
|
-
const artifactsToUpdate = [sourceArtifact, ...downstreamArtifacts];
|
|
708
|
-
const status = formatChangeStatus(context, isStoreSelectedRoot(root) ? { storeId: root.storeId } : {});
|
|
709
|
-
await trackArtifactModifyRequested({
|
|
710
|
-
changeDir: context.changeDir,
|
|
711
|
-
changeName,
|
|
712
|
-
schema: context.schemaName,
|
|
713
|
-
sourceArtifactId: sourceArtifact,
|
|
714
|
-
downstreamArtifactIds: downstreamArtifacts,
|
|
715
|
-
artifactsToUpdate,
|
|
716
|
-
modifyInput,
|
|
717
|
-
editor,
|
|
718
|
-
});
|
|
719
|
-
const payload = {
|
|
720
|
-
changeName,
|
|
721
|
-
schemaName: context.schemaName,
|
|
722
|
-
sourceArtifact,
|
|
723
|
-
...(modifyInput ? { modifyInput } : {}),
|
|
724
|
-
downstreamArtifacts,
|
|
725
|
-
artifactsToUpdate,
|
|
726
|
-
changeRoot: context.changeDir,
|
|
727
|
-
artifactPaths: status.artifactPaths,
|
|
728
|
-
phase: 'pre_apply',
|
|
729
|
-
instruction: buildModifyInstruction(sourceArtifact, downstreamArtifacts, modifyInput),
|
|
730
|
-
};
|
|
528
|
+
const changeName = await validateChangeExists(options.change, root.path, root.changesDir, { newChangeHint: withStoreFlag(root, 'openspec new change <name>') });
|
|
529
|
+
const projectConfig = readProjectConfig(root.path);
|
|
530
|
+
const instructions = generateArchiveInstructions(changeName, projectConfig);
|
|
731
531
|
spinner?.stop();
|
|
732
532
|
if (options.json) {
|
|
733
|
-
console.log(JSON.stringify({ ...
|
|
533
|
+
console.log(JSON.stringify({ ...instructions, root: toRootOutput(root) }, null, 2));
|
|
734
534
|
return;
|
|
735
535
|
}
|
|
736
|
-
|
|
536
|
+
printArchiveInstructionsText(instructions);
|
|
737
537
|
}
|
|
738
538
|
catch (error) {
|
|
739
539
|
spinner?.stop();
|
|
740
540
|
throw error;
|
|
741
541
|
}
|
|
742
542
|
}
|
|
743
|
-
export function
|
|
744
|
-
console.log(`##
|
|
745
|
-
console.log(`Schema: ${instructions.schemaName}`);
|
|
746
|
-
console.log(`Phase: ${instructions.phase}`);
|
|
543
|
+
export function printArchiveInstructionsText(instructions) {
|
|
544
|
+
console.log(`## Archive Inputs: ${instructions.changeName}`);
|
|
747
545
|
console.log();
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
546
|
+
printOperationInputsText(instructions);
|
|
547
|
+
}
|
|
548
|
+
function printOperationInputsText(inputs) {
|
|
549
|
+
if (inputs.context) {
|
|
550
|
+
console.log('### Project Context (required instruction input)');
|
|
551
|
+
console.log(inputs.context);
|
|
552
|
+
console.log();
|
|
553
|
+
}
|
|
554
|
+
if (inputs.operationGuidance && inputs.operationGuidance.length > 0) {
|
|
555
|
+
console.log('### Operation Guidance (advisory)');
|
|
556
|
+
for (const guidance of inputs.operationGuidance) {
|
|
557
|
+
console.log(`- ${guidance}`);
|
|
558
|
+
}
|
|
559
|
+
console.log();
|
|
560
|
+
}
|
|
561
|
+
if (!inputs.context && !inputs.operationGuidance) {
|
|
562
|
+
console.log('No project context or operation guidance configured.');
|
|
751
563
|
}
|
|
752
|
-
console.log(`Downstream: ${instructions.downstreamArtifacts.length > 0 ? instructions.downstreamArtifacts.join(', ') : '(none)'}`);
|
|
753
|
-
console.log(`Artifacts to update: ${instructions.artifactsToUpdate.join(', ')}`);
|
|
754
|
-
console.log();
|
|
755
|
-
console.log('### Instruction');
|
|
756
|
-
console.log(instructions.instruction);
|
|
757
564
|
}
|
|
758
565
|
//# sourceMappingURL=instructions.js.map
|