@codewalla_india/openspec 1.3.1 → 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 -191
- 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 -224
- 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 -73
- 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
|
@@ -14,10 +14,6 @@ export interface NewChangeOptions {
|
|
|
14
14
|
storePath?: string;
|
|
15
15
|
initiative?: string;
|
|
16
16
|
areas?: string;
|
|
17
|
-
entryPoint?: string;
|
|
18
|
-
workflowInput?: string;
|
|
19
|
-
workflowInputFile?: string;
|
|
20
|
-
editor?: string;
|
|
21
17
|
json?: boolean;
|
|
22
18
|
}
|
|
23
19
|
export declare function newChangeCommand(name: string | undefined, options: NewChangeOptions): Promise<void>;
|
|
@@ -8,19 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import ora from 'ora';
|
|
10
10
|
import path from 'path';
|
|
11
|
-
import { trackWorkflowStarted, trackCommandFailed, normalizeEditor, resolveWorkflowInputAsync, } from '../../telemetry/index.js';
|
|
12
11
|
import { createChange, validateChangeName } from '../../utils/change-utils.js';
|
|
13
12
|
import { formatChangeLocation } from '../../core/planning-home.js';
|
|
14
13
|
import { resolveRootForCommand, RootSelectionError, toPlanningHome, toRootOutput, withStoreFlag, isStoreSelectedRoot, } from '../../core/root-selection.js';
|
|
15
14
|
import { printJson, statusFromError, validateSchemaExists } from './shared.js';
|
|
16
|
-
|
|
17
|
-
function resolveEntryPoint(value) {
|
|
18
|
-
const normalized = (value ?? 'manual').toLowerCase();
|
|
19
|
-
if (VALID_ENTRY_POINTS.has(normalized)) {
|
|
20
|
-
return normalized;
|
|
21
|
-
}
|
|
22
|
-
throw new Error(`Invalid --entry-point "${value}". Use: propose, new, ff, or manual.`);
|
|
23
|
-
}
|
|
15
|
+
import { trackWorkflowStarted } from '../../telemetry/index.js';
|
|
24
16
|
// -----------------------------------------------------------------------------
|
|
25
17
|
// Command Implementation
|
|
26
18
|
// -----------------------------------------------------------------------------
|
|
@@ -33,6 +25,8 @@ function assertRemovedOptionsAbsent(options) {
|
|
|
33
25
|
}
|
|
34
26
|
}
|
|
35
27
|
function printCreatedChangeHuman(payload, root) {
|
|
28
|
+
// A relative path is only honest when the root is where the user
|
|
29
|
+
// stands; a distant ancestor root gets the absolute path.
|
|
36
30
|
const location = !isStoreSelectedRoot(root) && root.path === process.cwd()
|
|
37
31
|
? formatChangeLocation(toPlanningHome(root), payload.change.id)
|
|
38
32
|
: payload.change.path;
|
|
@@ -59,12 +53,7 @@ export async function newChangeCommand(name, options) {
|
|
|
59
53
|
return;
|
|
60
54
|
}
|
|
61
55
|
const projectRoot = root.path;
|
|
62
|
-
|
|
63
|
-
const editor = normalizeEditor(options.editor);
|
|
64
|
-
const workflowInput = await resolveWorkflowInputAsync({
|
|
65
|
-
workflowInput: options.workflowInput,
|
|
66
|
-
workflowInputFile: options.workflowInputFile,
|
|
67
|
-
});
|
|
56
|
+
// Validate schema if provided
|
|
68
57
|
if (options.schema) {
|
|
69
58
|
validateSchemaExists(options.schema, projectRoot);
|
|
70
59
|
}
|
|
@@ -80,23 +69,12 @@ export async function newChangeCommand(name, options) {
|
|
|
80
69
|
...(options.goal ? { goal: options.goal } : {}),
|
|
81
70
|
},
|
|
82
71
|
});
|
|
72
|
+
// If description provided, create README.md with description
|
|
83
73
|
if (options.description) {
|
|
84
74
|
const { promises: fs } = await import('fs');
|
|
85
75
|
const readmePath = path.join(result.changeDir, 'README.md');
|
|
86
76
|
await fs.writeFile(readmePath, `# ${name}\n\n${options.description}\n`, 'utf-8');
|
|
87
77
|
}
|
|
88
|
-
await trackWorkflowStarted({
|
|
89
|
-
changeDir: result.changeDir,
|
|
90
|
-
changeName: name,
|
|
91
|
-
schema: result.schema,
|
|
92
|
-
entryPoint,
|
|
93
|
-
storeSelected: isStoreSelectedRoot(root),
|
|
94
|
-
projectRoot,
|
|
95
|
-
workflowInput,
|
|
96
|
-
description: options.description,
|
|
97
|
-
goal: options.goal,
|
|
98
|
-
editor,
|
|
99
|
-
});
|
|
100
78
|
const payload = {
|
|
101
79
|
change: {
|
|
102
80
|
id: name,
|
|
@@ -112,9 +90,13 @@ export async function newChangeCommand(name, options) {
|
|
|
112
90
|
}
|
|
113
91
|
spinner?.stop();
|
|
114
92
|
printCreatedChangeHuman(payload, root);
|
|
93
|
+
// Track workflow started event
|
|
94
|
+
await trackWorkflowStarted(name, {
|
|
95
|
+
schema: resolvedSchema,
|
|
96
|
+
...(options.goal ? { goal: options.goal } : {}),
|
|
97
|
+
});
|
|
115
98
|
}
|
|
116
99
|
catch (error) {
|
|
117
|
-
await trackCommandFailed('new_change', error);
|
|
118
100
|
spinner?.stop();
|
|
119
101
|
if (options.json) {
|
|
120
102
|
printJson({
|
|
@@ -17,19 +17,6 @@ export interface TaskItem {
|
|
|
17
17
|
description: string;
|
|
18
18
|
done: boolean;
|
|
19
19
|
}
|
|
20
|
-
export interface ApplyComprehensionInfo {
|
|
21
|
-
required: boolean;
|
|
22
|
-
passed: boolean;
|
|
23
|
-
thresholdPercent: number;
|
|
24
|
-
bestScorePercent?: number;
|
|
25
|
-
questionCount: number;
|
|
26
|
-
questionAllocation: Record<string, number>;
|
|
27
|
-
optionsPerQuestion: number;
|
|
28
|
-
requirementCount: number;
|
|
29
|
-
scenarioCount: number;
|
|
30
|
-
pendingTaskCount: number;
|
|
31
|
-
attempts?: number;
|
|
32
|
-
}
|
|
33
20
|
export interface ApplyInstructions {
|
|
34
21
|
changeName: string;
|
|
35
22
|
changeDir: string;
|
|
@@ -41,16 +28,44 @@ export interface ApplyInstructions {
|
|
|
41
28
|
remaining: number;
|
|
42
29
|
};
|
|
43
30
|
tasks: TaskItem[];
|
|
44
|
-
state: 'blocked' | 'all_done' | 'ready';
|
|
31
|
+
state: 'blocked' | 'all_done' | 'ready' | 'comprehension_blocked';
|
|
45
32
|
missingArtifacts?: string[];
|
|
46
|
-
missingComprehension?: boolean;
|
|
47
|
-
comprehension?: ApplyComprehensionInfo;
|
|
48
33
|
instruction: string;
|
|
49
34
|
/** Referenced-store index (read-only upstream context; omitted when none declared) */
|
|
50
35
|
references?: ReferenceIndexEntry[];
|
|
51
|
-
/**
|
|
36
|
+
/** Current project background from the selected root. */
|
|
37
|
+
context?: string;
|
|
38
|
+
/** Current advisory guidance for apply. */
|
|
39
|
+
operationGuidance?: string[];
|
|
40
|
+
/** Comprehension quiz status */
|
|
41
|
+
comprehension?: {
|
|
42
|
+
required: boolean;
|
|
43
|
+
passed: boolean;
|
|
44
|
+
thresholdPercent: number;
|
|
45
|
+
questionCount: number;
|
|
46
|
+
questionAllocation: {
|
|
47
|
+
plan: number;
|
|
48
|
+
specs: number;
|
|
49
|
+
design: number;
|
|
50
|
+
proposal: number;
|
|
51
|
+
tasks: number;
|
|
52
|
+
};
|
|
53
|
+
optionsPerQuestion: number;
|
|
54
|
+
requirementCount: number;
|
|
55
|
+
scenarioCount: number;
|
|
56
|
+
pendingTaskCount: number;
|
|
57
|
+
attempts?: number;
|
|
58
|
+
};
|
|
59
|
+
/** Whether apply ready event was emitted */
|
|
52
60
|
applyReadyEmitted?: boolean;
|
|
53
61
|
}
|
|
62
|
+
export interface ArchiveInstructions {
|
|
63
|
+
changeName: string;
|
|
64
|
+
/** Current project background from the selected root. */
|
|
65
|
+
context?: string;
|
|
66
|
+
/** Current advisory guidance for archive. */
|
|
67
|
+
operationGuidance?: string[];
|
|
68
|
+
}
|
|
54
69
|
export declare const DEFAULT_SCHEMA = "spec-driven";
|
|
55
70
|
export declare function printJson(payload: unknown): void;
|
|
56
71
|
export declare function statusFromError(error: unknown): ChangeCommandStatus;
|
|
@@ -61,11 +76,11 @@ export declare function isColorDisabled(): boolean;
|
|
|
61
76
|
/**
|
|
62
77
|
* Gets the color function based on status.
|
|
63
78
|
*/
|
|
64
|
-
export declare function getStatusColor(status: 'done' | 'ready' | 'blocked'): (text: string) => string;
|
|
79
|
+
export declare function getStatusColor(status: 'done' | 'skipped' | 'ready' | 'blocked'): (text: string) => string;
|
|
65
80
|
/**
|
|
66
81
|
* Gets the status indicator for an artifact.
|
|
67
82
|
*/
|
|
68
|
-
export declare function getStatusIndicator(status: 'done' | 'ready' | 'blocked'): string;
|
|
83
|
+
export declare function getStatusIndicator(status: 'done' | 'skipped' | 'ready' | 'blocked'): string;
|
|
69
84
|
/**
|
|
70
85
|
* Returns the list of available change directory names under openspec/changes/.
|
|
71
86
|
* Excludes the archive directory and hidden directories.
|
|
@@ -9,7 +9,6 @@ import path from 'path';
|
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import { getSchemaDir, listSchemas } from '../../core/artifact-graph/index.js';
|
|
11
11
|
import { isRootSelectionError } from '../../core/root-selection.js';
|
|
12
|
-
import { validateChangeName } from '../../utils/change-utils.js';
|
|
13
12
|
// -----------------------------------------------------------------------------
|
|
14
13
|
// Constants
|
|
15
14
|
// -----------------------------------------------------------------------------
|
|
@@ -46,6 +45,8 @@ export function getStatusColor(status) {
|
|
|
46
45
|
switch (status) {
|
|
47
46
|
case 'done':
|
|
48
47
|
return chalk.green;
|
|
48
|
+
case 'skipped':
|
|
49
|
+
return chalk.gray;
|
|
49
50
|
case 'ready':
|
|
50
51
|
return chalk.yellow;
|
|
51
52
|
case 'blocked':
|
|
@@ -60,6 +61,8 @@ export function getStatusIndicator(status) {
|
|
|
60
61
|
switch (status) {
|
|
61
62
|
case 'done':
|
|
62
63
|
return color('[x]');
|
|
64
|
+
case 'skipped':
|
|
65
|
+
return color('[~]');
|
|
63
66
|
case 'ready':
|
|
64
67
|
return color('[ ]');
|
|
65
68
|
case 'blocked':
|
|
@@ -84,6 +87,33 @@ export async function getAvailableChanges(projectRoot, changesDir = path.join(pr
|
|
|
84
87
|
throw error;
|
|
85
88
|
}
|
|
86
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Validates a change name used to look up an existing change directory.
|
|
92
|
+
* Lookup accepts any directory name that `getAvailableChanges` could return
|
|
93
|
+
* (the kebab-case convention in `validateChangeName` applies at creation
|
|
94
|
+
* time only); it only rejects names that would escape the changes directory
|
|
95
|
+
* or address entries `getAvailableChanges` excludes (hidden dirs, archive).
|
|
96
|
+
*
|
|
97
|
+
* @returns An error message, or undefined if the name is safe to look up
|
|
98
|
+
*/
|
|
99
|
+
function validateChangeLookupName(changeName) {
|
|
100
|
+
if (changeName === '.' || changeName === '..') {
|
|
101
|
+
return 'Change name cannot be a relative path segment';
|
|
102
|
+
}
|
|
103
|
+
if (changeName.includes('/') || changeName.includes('\\')) {
|
|
104
|
+
return 'Change name cannot contain path separators';
|
|
105
|
+
}
|
|
106
|
+
if (changeName.includes('\0')) {
|
|
107
|
+
return 'Change name cannot contain null characters';
|
|
108
|
+
}
|
|
109
|
+
if (changeName.startsWith('.')) {
|
|
110
|
+
return 'Change name cannot start with a dot';
|
|
111
|
+
}
|
|
112
|
+
if (changeName === 'archive') {
|
|
113
|
+
return "'archive' is reserved for archived changes";
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
87
117
|
/**
|
|
88
118
|
* Validates that a change exists and returns available changes if not.
|
|
89
119
|
* Checks directory existence directly to support scaffolded changes (without proposal.md).
|
|
@@ -100,9 +130,9 @@ export async function validateChangeExists(changeName, projectRoot, changesDir =
|
|
|
100
130
|
throw new Error(`Missing required option --change. Available changes:\n ${available.join('\n ')}`);
|
|
101
131
|
}
|
|
102
132
|
// Validate change name format to prevent path traversal
|
|
103
|
-
const
|
|
104
|
-
if (
|
|
105
|
-
throw new Error(`Invalid change name '${changeName}': ${
|
|
133
|
+
const lookupError = validateChangeLookupName(changeName);
|
|
134
|
+
if (lookupError) {
|
|
135
|
+
throw new Error(`Invalid change name '${changeName}': ${lookupError}`);
|
|
106
136
|
}
|
|
107
137
|
// Check directory existence directly
|
|
108
138
|
const changePath = path.join(changesDir, changeName);
|
|
@@ -7,8 +7,7 @@ import ora from 'ora';
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { getChangeDir } from '../../core/planning-home.js';
|
|
9
9
|
import { resolveRootForCommand, toPlanningHome, toRootOutput, withStoreFlag, isStoreSelectedRoot, } from '../../core/root-selection.js';
|
|
10
|
-
import { loadChangeContext, formatChangeStatus,
|
|
11
|
-
import { maybeEmitProposalReady, trackArtifactContentChanges, } from '../../telemetry/index.js';
|
|
10
|
+
import { loadChangeContext, formatChangeStatus, } from '../../core/artifact-graph/index.js';
|
|
12
11
|
import { validateChangeExists, validateSchemaExists, getAvailableChanges, getStatusIndicator, getStatusColor, } from './shared.js';
|
|
13
12
|
// -----------------------------------------------------------------------------
|
|
14
13
|
// Command Implementation
|
|
@@ -54,32 +53,6 @@ export async function statusCommand(options) {
|
|
|
54
53
|
planningHome,
|
|
55
54
|
});
|
|
56
55
|
const status = formatChangeStatus(context, isStoreSelectedRoot(root) ? { storeId: root.storeId } : {});
|
|
57
|
-
const changeDir = getChangeDir(planningHome, changeName);
|
|
58
|
-
const schema = resolveSchema(context.schemaName, projectRoot);
|
|
59
|
-
const contextFiles = {};
|
|
60
|
-
for (const artifact of schema.artifacts) {
|
|
61
|
-
const outputs = resolveArtifactOutputs(changeDir, artifact.generates);
|
|
62
|
-
if (outputs.length > 0) {
|
|
63
|
-
contextFiles[artifact.id] = outputs;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
const applyConfig = schema.apply;
|
|
67
|
-
const requiredArtifactIds = applyConfig?.requires ?? schema.artifacts.map((a) => a.id);
|
|
68
|
-
const missingArtifacts = [];
|
|
69
|
-
for (const artifactId of requiredArtifactIds) {
|
|
70
|
-
const artifact = schema.artifacts.find((a) => a.id === artifactId);
|
|
71
|
-
if (artifact && resolveArtifactOutputs(changeDir, artifact.generates).length === 0) {
|
|
72
|
-
missingArtifacts.push(artifactId);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
await trackArtifactContentChanges({ changeDir, changeName, contextFiles });
|
|
76
|
-
await maybeEmitProposalReady({
|
|
77
|
-
changeDir,
|
|
78
|
-
changeName,
|
|
79
|
-
schema: context.schemaName,
|
|
80
|
-
missingArtifacts,
|
|
81
|
-
artifactCount: status.artifacts.length,
|
|
82
|
-
});
|
|
83
56
|
spinner?.stop();
|
|
84
57
|
if (options.json) {
|
|
85
58
|
console.log(JSON.stringify({ ...status, root: rootOutput }, null, 2));
|
|
@@ -94,26 +67,31 @@ export async function statusCommand(options) {
|
|
|
94
67
|
}
|
|
95
68
|
export function printStatusText(status) {
|
|
96
69
|
const doneCount = status.artifacts.filter((a) => a.status === 'done').length;
|
|
97
|
-
const
|
|
70
|
+
const skippedCount = status.artifacts.filter((a) => a.status === 'skipped').length;
|
|
71
|
+
const total = status.artifacts.length - skippedCount;
|
|
98
72
|
console.log(`Change: ${status.changeName}`);
|
|
99
73
|
console.log(`Schema: ${status.schemaName}`);
|
|
100
74
|
if (status.changeRoot) {
|
|
101
75
|
console.log(`Change root: ${status.changeRoot}`);
|
|
102
76
|
}
|
|
103
|
-
|
|
77
|
+
const skippedSuffix = skippedCount > 0 ? ` (${skippedCount} skipped)` : '';
|
|
78
|
+
console.log(`Progress: ${doneCount}/${total} artifacts complete${skippedSuffix}`);
|
|
104
79
|
console.log();
|
|
105
80
|
for (const artifact of status.artifacts) {
|
|
106
81
|
const indicator = getStatusIndicator(artifact.status);
|
|
107
82
|
const color = getStatusColor(artifact.status);
|
|
108
83
|
let line = `${indicator} ${artifact.id}`;
|
|
84
|
+
if (artifact.status === 'skipped') {
|
|
85
|
+
line += color(' (skipped: change declares skip_specs)');
|
|
86
|
+
}
|
|
109
87
|
if (artifact.status === 'blocked' && artifact.missingDeps && artifact.missingDeps.length > 0) {
|
|
110
88
|
line += color(` (blocked by: ${artifact.missingDeps.join(', ')})`);
|
|
111
89
|
}
|
|
112
90
|
console.log(line);
|
|
113
91
|
}
|
|
114
|
-
if (status.
|
|
92
|
+
if (status.isPlanningComplete) {
|
|
115
93
|
console.log();
|
|
116
|
-
console.log(chalk.green('All artifacts complete!'));
|
|
94
|
+
console.log(chalk.green('All planning artifacts complete!'));
|
|
117
95
|
}
|
|
118
96
|
}
|
|
119
97
|
//# sourceMappingURL=status.js.map
|
|
@@ -39,11 +39,21 @@ export async function templatesCommand(options) {
|
|
|
39
39
|
else {
|
|
40
40
|
source = 'package';
|
|
41
41
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
templatePath
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
const templatesDir = path.join(schemaDir, 'templates');
|
|
43
|
+
const templates = graph.getAllArtifacts().map((artifact) => {
|
|
44
|
+
const templatePath = path.join(templatesDir, artifact.template);
|
|
45
|
+
try {
|
|
46
|
+
FileSystemUtils.assertPathWithin(templatesDir, templatePath);
|
|
47
|
+
return {
|
|
48
|
+
artifactId: artifact.id,
|
|
49
|
+
templatePath: FileSystemUtils.canonicalizeExistingPath(templatePath),
|
|
50
|
+
source,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new Error(`Template '${artifact.template}' for artifact '${artifact.id}' points outside the schema templates directory`);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
47
57
|
spinner?.stop();
|
|
48
58
|
if (options.json) {
|
|
49
59
|
const output = {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as child_process from 'node:child_process';
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { type LaunchCommand } from '../core/openers.js';
|
|
4
4
|
interface LaunchResult {
|
|
@@ -6,7 +6,7 @@ interface LaunchResult {
|
|
|
6
6
|
signal: NodeJS.Signals | null;
|
|
7
7
|
}
|
|
8
8
|
export interface LaunchOptions {
|
|
9
|
-
spawnFn?: typeof
|
|
9
|
+
spawnFn?: typeof child_process.spawn;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Spawns the opener with this terminal's stdio. Resolves with the
|
package/dist/commands/workset.js
CHANGED
|
@@ -8,18 +8,19 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as os from 'node:os';
|
|
10
10
|
import { createRequire } from 'node:module';
|
|
11
|
-
import {
|
|
11
|
+
import { asErrorMessage, emitFailure, printJson } from './shared-output.js';
|
|
12
12
|
import { Option } from 'commander';
|
|
13
|
+
import { COMMAND_REGISTRY } from '../core/completions/command-registry.js';
|
|
13
14
|
import { buildWorksetCodeWorkspaceJson, getWorkset, getWorksetCodeWorkspacePath, listWorksets, readWorksetsState, removeWorkset, updateWorksetsState, validateWorksetName, withWorkset, withWorksetsLock, worksetNotFoundError, } from '../core/worksets.js';
|
|
15
|
+
import { finalizeWorkset, firstInstalledAlternative, formatMemberRows, noToolInstalledError, resolveMemberFlags, toolUnavailableError, toolUnknownError, } from './workset-input.js';
|
|
16
|
+
import { composeInteractively, confirmRemoveInteractively, promptOpenNow, promptToolFromChoices, } from './workset-prompts.js';
|
|
14
17
|
import { buildLaunchCommand, findOpener, isOpenerCommandAvailable, isOpenerEnabled, listOpenerChoices, mergeOpenerTable, } from '../core/openers.js';
|
|
15
18
|
import { pathIsDirectory, writeFileAtomically } from '../core/file-state.js';
|
|
16
19
|
import { getGlobalConfig, getGlobalConfigPath, } from '../core/global-config.js';
|
|
17
20
|
import { StoreError } from '../core/store/errors.js';
|
|
18
21
|
import { isInteractive } from '../utils/interactive.js';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { composeInteractively, confirmRemoveInteractively, promptOpenNow, promptToolFromChoices, } from './workset-prompts.js';
|
|
22
|
-
import { COMMAND_REGISTRY } from '../core/completions/command-registry.js';
|
|
22
|
+
import { isPromptCancellationError, } from './shared-output.js';
|
|
23
|
+
import { trackWorksetOperation } from '../telemetry/index.js';
|
|
23
24
|
// cross-spawn is CJS with no types and only `workset open` needs it -
|
|
24
25
|
// loaded lazily so every other CLI invocation skips its module graph.
|
|
25
26
|
let cachedSpawn;
|
|
@@ -108,16 +109,14 @@ class WorksetCommand {
|
|
|
108
109
|
workset = await this.composeFromFlags(name, options);
|
|
109
110
|
}
|
|
110
111
|
await updateWorksetsState((state) => withWorkset(state, workset));
|
|
111
|
-
trackEvent('workset_created', {
|
|
112
|
-
member_count: workset.members.length,
|
|
113
|
-
has_tool: Boolean(workset.tool),
|
|
114
|
-
});
|
|
115
112
|
if (options.json) {
|
|
116
113
|
printJson({ workset, status: [] });
|
|
117
114
|
return;
|
|
118
115
|
}
|
|
119
116
|
console.log('');
|
|
120
117
|
console.log(`Saved workset '${workset.name}' (${workset.members.length} member${workset.members.length === 1 ? '' : 's'}) to your machine.`);
|
|
118
|
+
// Track workset create operation
|
|
119
|
+
await trackWorksetOperation('create', workset.name);
|
|
121
120
|
if (interactive && workset.tool !== undefined && table !== undefined) {
|
|
122
121
|
const label = findOpener(table, workset.tool)?.label ?? workset.tool;
|
|
123
122
|
let openNow = false;
|
|
@@ -179,22 +178,23 @@ class WorksetCommand {
|
|
|
179
178
|
return;
|
|
180
179
|
}
|
|
181
180
|
if (worksets.length === 0) {
|
|
182
|
-
console.log('No worksets saved.
|
|
181
|
+
console.log('No worksets saved.');
|
|
182
|
+
console.log('');
|
|
183
|
+
console.log('Next:');
|
|
184
|
+
console.log(' openspec workset create <name> --member <path>');
|
|
183
185
|
return;
|
|
184
186
|
}
|
|
185
|
-
// The table is consulted only to render tool labels.
|
|
186
187
|
const table = worksets.some((workset) => workset.tool !== undefined)
|
|
187
188
|
? readOpenerTable()
|
|
188
189
|
: [];
|
|
189
190
|
for (const workset of worksets) {
|
|
190
|
-
|
|
191
|
-
? ` (opens in ${findOpener(table, workset.tool)?.label ?? workset.tool})`
|
|
192
|
-
: '';
|
|
193
|
-
console.log(`${workset.name}${toolLabel}`);
|
|
191
|
+
console.log(`${workset.name}${workset.tool !== undefined ? ` (opens in ${findOpener(table, workset.tool)?.label ?? workset.tool})` : ''}`);
|
|
194
192
|
for (const row of formatMemberRows(workset.members)) {
|
|
195
193
|
console.log(` ${row}`);
|
|
196
194
|
}
|
|
197
195
|
}
|
|
196
|
+
// Track workset list operation
|
|
197
|
+
await trackWorksetOperation('list');
|
|
198
198
|
}
|
|
199
199
|
catch (error) {
|
|
200
200
|
emitFailure(options.json, { worksets: [], status: [] }, error, 'workset_error');
|
|
@@ -285,11 +285,8 @@ class WorksetCommand {
|
|
|
285
285
|
else {
|
|
286
286
|
console.log(`Handing this terminal to ${opener.label} for '${name}' (the session ends when you exit).`);
|
|
287
287
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
member_count: prepared.surviving.length,
|
|
291
|
-
skipped_count: prepared.skipped.length,
|
|
292
|
-
});
|
|
288
|
+
// Track workset open operation
|
|
289
|
+
await trackWorksetOperation('open', name);
|
|
293
290
|
let result;
|
|
294
291
|
try {
|
|
295
292
|
result = await launchOpenerCommand(launch);
|
|
@@ -357,15 +354,15 @@ class WorksetCommand {
|
|
|
357
354
|
}
|
|
358
355
|
}
|
|
359
356
|
await removeWorkset(name);
|
|
360
|
-
trackEvent('workset_removed');
|
|
361
357
|
if (options.json) {
|
|
362
358
|
printJson({ removed: { name }, status: [] });
|
|
363
359
|
return;
|
|
364
360
|
}
|
|
365
361
|
console.log(`Removed workset '${name}'. Member folders were not touched.`);
|
|
362
|
+
// Track workset remove operation
|
|
363
|
+
await trackWorksetOperation('remove', name);
|
|
366
364
|
}
|
|
367
365
|
catch (error) {
|
|
368
|
-
await trackCommandFailed('workset_remove', error);
|
|
369
366
|
emitFailure(options.json, { removed: null, status: [] }, error, 'workset_error');
|
|
370
367
|
}
|
|
371
368
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fingerprint utilities for artifact content
|
|
3
|
+
*
|
|
4
|
+
* This module provides fingerprinting functionality for detecting changes in artifact
|
|
5
|
+
* content. It reuses fingerprint logic from src/core/archive.ts and is used to
|
|
6
|
+
* invalidate quiz passes when artifacts are modified.
|
|
7
|
+
*
|
|
8
|
+
* @module comprehension-quiz/fingerprint
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Generate SHA-256 fingerprint of a file
|
|
12
|
+
*
|
|
13
|
+
* @param filePath - Path to the file to fingerprint
|
|
14
|
+
* @returns SHA-256 hash of file content
|
|
15
|
+
*/
|
|
16
|
+
export declare function fingerprintFile(filePath: string): Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Generate SHA-256 fingerprint of a directory recursively
|
|
19
|
+
*
|
|
20
|
+
* @param dirPath - Path to the directory to fingerprint
|
|
21
|
+
* @returns SHA-256 hash of all file contents combined
|
|
22
|
+
*/
|
|
23
|
+
export declare function fingerprintDirectory(dirPath: string): Promise<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Generate combined fingerprint of multiple artifact files
|
|
26
|
+
*
|
|
27
|
+
* @param filePaths - Array of file paths to fingerprint
|
|
28
|
+
* @returns Combined SHA-256 hash of all file contents
|
|
29
|
+
*/
|
|
30
|
+
export declare function fingerprintArtifacts(filePaths: string[]): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Check if a pass record is valid for current artifact fingerprint
|
|
33
|
+
*
|
|
34
|
+
* @param changeName - Name of the change
|
|
35
|
+
* @param currentFingerprint - Current fingerprint of artifacts
|
|
36
|
+
* @returns True if pass record exists and fingerprint matches
|
|
37
|
+
*/
|
|
38
|
+
export declare function isPassRecordValid(changeName: string, currentFingerprint: string): Promise<boolean>;
|
|
39
|
+
//# sourceMappingURL=fingerprint.d.ts.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fingerprint utilities for artifact content
|
|
3
|
+
*
|
|
4
|
+
* This module provides fingerprinting functionality for detecting changes in artifact
|
|
5
|
+
* content. It reuses fingerprint logic from src/core/archive.ts and is used to
|
|
6
|
+
* invalidate quiz passes when artifacts are modified.
|
|
7
|
+
*
|
|
8
|
+
* @module comprehension-quiz/fingerprint
|
|
9
|
+
*/
|
|
10
|
+
import { createHash } from 'crypto';
|
|
11
|
+
import { promises as fs } from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
/**
|
|
14
|
+
* Generate SHA-256 fingerprint of a file
|
|
15
|
+
*
|
|
16
|
+
* @param filePath - Path to the file to fingerprint
|
|
17
|
+
* @returns SHA-256 hash of file content
|
|
18
|
+
*/
|
|
19
|
+
export async function fingerprintFile(filePath) {
|
|
20
|
+
const fileHash = createHash('sha256');
|
|
21
|
+
const content = await fs.readFile(filePath);
|
|
22
|
+
fileHash.update(content);
|
|
23
|
+
return fileHash.digest('hex');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Generate SHA-256 fingerprint of a directory recursively
|
|
27
|
+
*
|
|
28
|
+
* @param dirPath - Path to the directory to fingerprint
|
|
29
|
+
* @returns SHA-256 hash of all file contents combined
|
|
30
|
+
*/
|
|
31
|
+
export async function fingerprintDirectory(dirPath) {
|
|
32
|
+
const hash = createHash('sha256');
|
|
33
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
36
|
+
if (entry.isDirectory()) {
|
|
37
|
+
// Recursively fingerprint subdirectories
|
|
38
|
+
const dirHash = await fingerprintDirectory(fullPath);
|
|
39
|
+
hash.update(dirHash);
|
|
40
|
+
}
|
|
41
|
+
else if (entry.isFile()) {
|
|
42
|
+
// Fingerprint file content
|
|
43
|
+
const fileHash = await fingerprintFile(fullPath);
|
|
44
|
+
hash.update(fileHash);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return hash.digest('hex');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Generate combined fingerprint of multiple artifact files
|
|
51
|
+
*
|
|
52
|
+
* @param filePaths - Array of file paths to fingerprint
|
|
53
|
+
* @returns Combined SHA-256 hash of all file contents
|
|
54
|
+
*/
|
|
55
|
+
export async function fingerprintArtifacts(filePaths) {
|
|
56
|
+
const hash = createHash('sha256');
|
|
57
|
+
for (const filePath of filePaths) {
|
|
58
|
+
try {
|
|
59
|
+
const fileHash = await fingerprintFile(filePath);
|
|
60
|
+
hash.update(fileHash);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
// Skip files that don't exist or can't be read
|
|
64
|
+
console.warn(`Warning: Could not fingerprint ${filePath}: ${error}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return hash.digest('hex');
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if a pass record is valid for current artifact fingerprint
|
|
71
|
+
*
|
|
72
|
+
* @param changeName - Name of the change
|
|
73
|
+
* @param currentFingerprint - Current fingerprint of artifacts
|
|
74
|
+
* @returns True if pass record exists and fingerprint matches
|
|
75
|
+
*/
|
|
76
|
+
export async function isPassRecordValid(changeName, currentFingerprint) {
|
|
77
|
+
const { getPassRecord } = await import('./pass-record.js');
|
|
78
|
+
const record = await getPassRecord(changeName);
|
|
79
|
+
if (!record) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return record.fingerprint === currentFingerprint;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehension quiz system main entry point
|
|
3
|
+
*
|
|
4
|
+
* This module provides the main entry point for the comprehension quiz system,
|
|
5
|
+
* which enforces that developers read and understand change artifacts before
|
|
6
|
+
* implementation. The quiz blocks the `/opsx:apply` command until passed with a score
|
|
7
|
+
* of 80% or higher.
|
|
8
|
+
*
|
|
9
|
+
* @module comprehension-quiz
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { QuizExecutor } from './comprehension-quiz/quiz-executor.js';
|
|
14
|
+
* import { generateQuizQuestions } from './comprehension-quiz/question-generator.js';
|
|
15
|
+
* import { createProviderWithFallback } from './comprehension-quiz/providers/factory.js';
|
|
16
|
+
*
|
|
17
|
+
* // Generate questions
|
|
18
|
+
* const questions = await generateQuizQuestions(artifactPaths, 10);
|
|
19
|
+
*
|
|
20
|
+
* // Execute quiz
|
|
21
|
+
* const executor = new QuizExecutor();
|
|
22
|
+
* const result = await executor.executeQuiz(questions);
|
|
23
|
+
*
|
|
24
|
+
* if (result.passed) {
|
|
25
|
+
* console.log('Quiz passed! Proceeding with implementation.');
|
|
26
|
+
* } else {
|
|
27
|
+
* console.log('Quiz failed. Please review the artifacts and try again.');
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export * from './types.js';
|
|
32
|
+
export * from './providers/interface.js';
|
|
33
|
+
export * from './providers/factory.js';
|
|
34
|
+
export { OpenAIProvider } from './providers/openai.js';
|
|
35
|
+
export { AnthropicProvider } from './providers/anthropic.js';
|
|
36
|
+
export { OllamaProvider } from './providers/ollama.js';
|
|
37
|
+
export { LocalProvider } from './providers/local.js';
|
|
38
|
+
export { fingerprintFile, fingerprintDirectory, fingerprintArtifacts, isPassRecordValid } from './fingerprint.js';
|
|
39
|
+
export * from './pass-record.js';
|
|
40
|
+
export { QuizExecutor } from './quiz-executor.js';
|
|
41
|
+
export { generateQuizQuestions, generateQuestionsForArtifact, getQuestionCount, calculateQuestionAllocation, } from './question-generator.js';
|
|
42
|
+
//# sourceMappingURL=index.d.ts.map
|