@fission-ai/openspec 1.4.1 → 1.6.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -4
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +95 -95
- package/dist/commands/change.d.ts +5 -2
- package/dist/commands/change.js +23 -55
- package/dist/commands/config.js +6 -65
- package/dist/commands/context.d.ts +3 -0
- package/dist/commands/context.js +155 -0
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.js +163 -0
- package/dist/commands/shared-gather.d.ts +14 -0
- package/dist/commands/shared-gather.js +31 -0
- package/dist/commands/shared-output.d.ts +18 -0
- package/dist/commands/shared-output.js +61 -0
- package/dist/commands/show.d.ts +11 -6
- package/dist/commands/show.js +70 -25
- package/dist/commands/spec.d.ts +5 -1
- package/dist/commands/spec.js +15 -4
- package/dist/commands/store.d.ts +3 -0
- package/dist/commands/store.js +547 -0
- package/dist/commands/validate.d.ts +10 -0
- package/dist/commands/validate.js +88 -40
- package/dist/commands/workflow/index.d.ts +0 -2
- package/dist/commands/workflow/index.js +0 -1
- package/dist/commands/workflow/instructions.d.ts +11 -1
- package/dist/commands/workflow/instructions.js +71 -22
- package/dist/commands/workflow/new-change.d.ts +6 -3
- package/dist/commands/workflow/new-change.js +42 -77
- package/dist/commands/workflow/shared.d.ts +15 -3
- package/dist/commands/workflow/shared.js +20 -3
- package/dist/commands/workflow/status.d.ts +2 -0
- package/dist/commands/workflow/status.js +19 -17
- package/dist/commands/workset-input.d.ts +19 -0
- package/dist/commands/workset-input.js +112 -0
- package/dist/commands/workset-prompts.d.ts +12 -0
- package/dist/commands/workset-prompts.js +143 -0
- package/dist/commands/workset.d.ts +25 -0
- package/dist/commands/workset.js +446 -0
- package/dist/core/archive.d.ts +17 -6
- package/dist/core/archive.js +220 -68
- package/dist/core/artifact-graph/index.d.ts +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +18 -13
- package/dist/core/artifact-graph/instruction-loader.js +8 -31
- package/dist/core/change-metadata/schema.d.ts +1 -0
- package/dist/core/change-metadata/schema.js +4 -2
- package/dist/core/change-status-policy.d.ts +5 -18
- package/dist/core/change-status-policy.js +2 -37
- package/dist/core/command-generation/adapters/bob.js +1 -14
- package/dist/core/command-generation/adapters/claude.d.ts +1 -1
- package/dist/core/command-generation/adapters/claude.js +4 -15
- package/dist/core/command-generation/adapters/cursor.js +1 -14
- package/dist/core/command-generation/adapters/index.d.ts +2 -0
- package/dist/core/command-generation/adapters/index.js +2 -0
- package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
- package/dist/core/command-generation/adapters/oh-my-pi.js +44 -0
- package/dist/core/command-generation/adapters/pi.js +1 -14
- package/dist/core/command-generation/adapters/trae.d.ts +13 -0
- package/dist/core/command-generation/adapters/trae.js +48 -0
- package/dist/core/command-generation/adapters/windsurf.js +1 -14
- package/dist/core/command-generation/registry.js +4 -0
- package/dist/core/command-generation/yaml.d.ts +22 -0
- package/dist/core/command-generation/yaml.js +38 -0
- package/dist/core/completions/command-registry.js +87 -289
- package/dist/core/completions/generators/zsh-generator.js +3 -1
- package/dist/core/completions/installers/bash-installer.js +3 -0
- package/dist/core/completions/installers/fish-installer.js +8 -0
- package/dist/core/completions/installers/powershell-installer.js +30 -2
- package/dist/core/completions/installers/zsh-installer.js +3 -0
- package/dist/core/completions/shared-flags.d.ts +1 -0
- package/dist/core/completions/shared-flags.js +5 -0
- package/dist/core/config-schema.d.ts +2 -1
- package/dist/core/config-schema.js +26 -0
- package/dist/core/config.js +1 -0
- package/dist/core/file-state.d.ts +36 -0
- package/dist/core/file-state.js +112 -0
- package/dist/core/global-config.d.ts +2 -0
- package/dist/core/id.d.ts +17 -0
- package/dist/core/id.js +30 -0
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +3 -3
- package/dist/core/init.js +27 -6
- package/dist/core/list.d.ts +2 -0
- package/dist/core/list.js +37 -14
- package/dist/core/openers.d.ts +77 -0
- package/dist/core/openers.js +251 -0
- package/dist/core/openspec-root.d.ts +45 -0
- package/dist/core/openspec-root.js +195 -0
- package/dist/core/parsers/change-parser.js +2 -1
- package/dist/core/parsers/markdown-parser.d.ts +0 -3
- package/dist/core/parsers/markdown-parser.js +5 -59
- package/dist/core/parsers/requirement-blocks.d.ts +11 -0
- package/dist/core/parsers/requirement-blocks.js +42 -8
- package/dist/core/parsers/requirement-text.d.ts +55 -0
- package/dist/core/parsers/requirement-text.js +136 -0
- package/dist/core/planning-home.d.ts +1 -6
- package/dist/core/planning-home.js +2 -43
- package/dist/core/profile-sync-drift.js +1 -0
- package/dist/core/profiles.d.ts +2 -2
- package/dist/core/profiles.js +2 -1
- package/dist/core/project-config.d.ts +44 -1
- package/dist/core/project-config.js +160 -10
- package/dist/core/references.d.ts +63 -0
- package/dist/core/references.js +310 -0
- package/dist/core/relationship-health.d.ts +65 -0
- package/dist/core/relationship-health.js +64 -0
- package/dist/core/root-selection.d.ts +122 -0
- package/dist/core/root-selection.js +337 -0
- package/dist/core/schemas/base.schema.js +7 -2
- package/dist/core/shared/allowed-tools.d.ts +12 -0
- package/dist/core/shared/allowed-tools.js +12 -0
- package/dist/core/shared/skill-generation.js +5 -1
- package/dist/core/shared/tool-detection.d.ts +2 -2
- package/dist/core/shared/tool-detection.js +2 -0
- package/dist/core/specs-apply.d.ts +6 -1
- package/dist/core/specs-apply.js +41 -5
- package/dist/core/store/errors.d.ts +20 -0
- package/dist/core/{context-store → store}/errors.js +3 -3
- package/dist/core/store/foundation.d.ts +56 -0
- package/dist/core/store/foundation.js +251 -0
- package/dist/core/store/git.d.ts +23 -0
- package/dist/core/store/git.js +137 -0
- package/dist/core/{context-store → store}/index.d.ts +0 -1
- package/dist/core/{context-store → store}/index.js +0 -1
- package/dist/core/store/operations.d.ts +114 -0
- package/dist/core/store/operations.js +803 -0
- package/dist/core/store/registry.d.ts +58 -0
- package/dist/core/store/registry.js +275 -0
- package/dist/core/templates/skill-templates.d.ts +1 -0
- package/dist/core/templates/skill-templates.js +1 -0
- package/dist/core/templates/workflows/apply-change.js +5 -4
- package/dist/core/templates/workflows/archive-change.js +5 -4
- package/dist/core/templates/workflows/bulk-archive-change.js +5 -4
- package/dist/core/templates/workflows/continue-change.js +7 -2
- package/dist/core/templates/workflows/explore.js +5 -0
- package/dist/core/templates/workflows/ff-change.js +5 -0
- package/dist/core/templates/workflows/new-change.js +5 -0
- package/dist/core/templates/workflows/onboard.js +4 -1
- package/dist/core/templates/workflows/propose.js +5 -0
- package/dist/core/templates/workflows/store-selection.d.ts +8 -0
- package/dist/core/templates/workflows/store-selection.js +8 -0
- package/dist/core/templates/workflows/sync-specs.js +5 -4
- package/dist/core/templates/workflows/update-change.d.ts +10 -0
- package/dist/core/templates/workflows/update-change.js +167 -0
- package/dist/core/templates/workflows/verify-change.js +5 -4
- package/dist/core/update.js +4 -4
- package/dist/core/validation/validator.d.ts +7 -0
- package/dist/core/validation/validator.js +101 -38
- package/dist/core/view.js +1 -1
- package/dist/core/working-set.d.ts +47 -0
- package/dist/core/working-set.js +43 -0
- package/dist/core/worksets.d.ts +75 -0
- package/dist/core/worksets.js +245 -0
- package/dist/core/zod-issues.d.ts +4 -0
- package/dist/core/zod-issues.js +10 -0
- package/dist/utils/change-metadata.d.ts +1 -0
- package/dist/utils/change-metadata.js +1 -1
- package/dist/utils/change-utils.js +15 -0
- package/dist/utils/file-system.js +36 -17
- package/dist/utils/task-progress.d.ts +10 -1
- package/dist/utils/task-progress.js +66 -2
- package/package.json +26 -23
- package/dist/commands/context-store.d.ts +0 -3
- package/dist/commands/context-store.js +0 -475
- package/dist/commands/initiative.d.ts +0 -13
- package/dist/commands/initiative.js +0 -318
- package/dist/commands/workflow/initiative-link.d.ts +0 -24
- package/dist/commands/workflow/initiative-link.js +0 -47
- package/dist/commands/workflow/set-change.d.ts +0 -13
- package/dist/commands/workflow/set-change.js +0 -87
- package/dist/commands/workspace/context-status.d.ts +0 -4
- package/dist/commands/workspace/context-status.js +0 -59
- package/dist/commands/workspace/open-target-selection.d.ts +0 -13
- package/dist/commands/workspace/open-target-selection.js +0 -146
- package/dist/commands/workspace/open-view.d.ts +0 -62
- package/dist/commands/workspace/open-view.js +0 -249
- package/dist/commands/workspace/open.d.ts +0 -37
- package/dist/commands/workspace/open.js +0 -110
- package/dist/commands/workspace/opener-selection.d.ts +0 -11
- package/dist/commands/workspace/opener-selection.js +0 -98
- package/dist/commands/workspace/operations.d.ts +0 -29
- package/dist/commands/workspace/operations.js +0 -543
- package/dist/commands/workspace/prompt-theme.d.ts +0 -29
- package/dist/commands/workspace/prompt-theme.js +0 -24
- package/dist/commands/workspace/registration.d.ts +0 -13
- package/dist/commands/workspace/registration.js +0 -84
- package/dist/commands/workspace/selection.d.ts +0 -8
- package/dist/commands/workspace/selection.js +0 -104
- package/dist/commands/workspace/setup-prompts.d.ts +0 -13
- package/dist/commands/workspace/setup-prompts.js +0 -121
- package/dist/commands/workspace/types.d.ts +0 -103
- package/dist/commands/workspace/types.js +0 -36
- package/dist/commands/workspace.d.ts +0 -5
- package/dist/commands/workspace.js +0 -574
- package/dist/core/collections/index.d.ts +0 -3
- package/dist/core/collections/index.js +0 -3
- package/dist/core/collections/initiatives/collection.d.ts +0 -4
- package/dist/core/collections/initiatives/collection.js +0 -17
- package/dist/core/collections/initiatives/index.d.ts +0 -6
- package/dist/core/collections/initiatives/index.js +0 -6
- package/dist/core/collections/initiatives/operations.d.ts +0 -49
- package/dist/core/collections/initiatives/operations.js +0 -175
- package/dist/core/collections/initiatives/resolution.d.ts +0 -87
- package/dist/core/collections/initiatives/resolution.js +0 -374
- package/dist/core/collections/initiatives/schema.d.ts +0 -41
- package/dist/core/collections/initiatives/schema.js +0 -134
- package/dist/core/collections/initiatives/templates.d.ts +0 -12
- package/dist/core/collections/initiatives/templates.js +0 -90
- package/dist/core/collections/runtime.d.ts +0 -46
- package/dist/core/collections/runtime.js +0 -194
- package/dist/core/context-store/binding.d.ts +0 -53
- package/dist/core/context-store/binding.js +0 -197
- package/dist/core/context-store/errors.d.ts +0 -20
- package/dist/core/context-store/foundation.d.ts +0 -55
- package/dist/core/context-store/foundation.js +0 -321
- package/dist/core/context-store/operations.d.ts +0 -85
- package/dist/core/context-store/operations.js +0 -528
- package/dist/core/context-store/registry.d.ts +0 -45
- package/dist/core/context-store/registry.js +0 -229
- package/dist/core/workspace/foundation.d.ts +0 -67
- package/dist/core/workspace/foundation.js +0 -295
- package/dist/core/workspace/index.d.ts +0 -8
- package/dist/core/workspace/index.js +0 -8
- package/dist/core/workspace/legacy-state.d.ts +0 -28
- package/dist/core/workspace/legacy-state.js +0 -200
- package/dist/core/workspace/link-input.d.ts +0 -9
- package/dist/core/workspace/link-input.js +0 -32
- package/dist/core/workspace/open-surface.d.ts +0 -45
- package/dist/core/workspace/open-surface.js +0 -215
- package/dist/core/workspace/openers.d.ts +0 -21
- package/dist/core/workspace/openers.js +0 -124
- package/dist/core/workspace/registry.d.ts +0 -24
- package/dist/core/workspace/registry.js +0 -146
- package/dist/core/workspace/skills.d.ts +0 -57
- package/dist/core/workspace/skills.js +0 -334
- package/dist/core/workspace/state-io.d.ts +0 -10
- package/dist/core/workspace/state-io.js +0 -121
- package/schemas/workspace-planning/schema.yaml +0 -72
- package/schemas/workspace-planning/templates/design.md +0 -33
- package/schemas/workspace-planning/templates/proposal.md +0 -28
- package/schemas/workspace-planning/templates/spec.md +0 -9
- package/schemas/workspace-planning/templates/tasks.md +0 -15
package/README.md
CHANGED
|
@@ -47,6 +47,14 @@ Our philosophy:
|
|
|
47
47
|
## See it in action
|
|
48
48
|
|
|
49
49
|
```text
|
|
50
|
+
You: /opsx:explore
|
|
51
|
+
AI: What would you like to explore?
|
|
52
|
+
You: I want dark mode but I'm not sure how to do it cleanly.
|
|
53
|
+
AI: Let me look at your styling setup...
|
|
54
|
+
Cleanest path here: CSS variables + a small theme context,
|
|
55
|
+
with system-preference detection. No new dependencies. Scope it?
|
|
56
|
+
You: Yes, let's do it.
|
|
57
|
+
|
|
50
58
|
You: /opsx:propose add-dark-mode
|
|
51
59
|
AI: Created openspec/changes/add-dark-mode/
|
|
52
60
|
✓ proposal.md — why we're doing this, what's changing
|
|
@@ -77,6 +85,18 @@ AI: Archived to openspec/changes/archive/2025-01-23-add-dark-mode/
|
|
|
77
85
|
|
|
78
86
|
</details>
|
|
79
87
|
|
|
88
|
+
## Why teams adopt OpenSpec
|
|
89
|
+
|
|
90
|
+
Solo, OpenSpec keeps you and your AI honest on a single repo. On a team, the hard part moves: a feature spans the API server, the web app, and a shared library; requirements are owned by one team and consumed by others; planning starts before any code exists.
|
|
91
|
+
|
|
92
|
+
**[Stores](docs/stores-beta/user-guide.md)** are the answer — planning in a repo of its own. The same `openspec/` shape you already know (specs and changes), shared by `git push` like anything else. One source of truth your whole team and every coding agent can read, across every repo.
|
|
93
|
+
|
|
94
|
+
- **Cross-repo features** — one change, one plan, even when the code lands in three repos.
|
|
95
|
+
- **Shared requirements** — a platform team owns the specs; product teams reference them read-only, right where their coding agent can read them. No drifting wiki.
|
|
96
|
+
- **Plan before code** — capture the plan in the store now; the code repos catch up later.
|
|
97
|
+
|
|
98
|
+
> Stores are in **beta**. Start with the [Stores User Guide](docs/stores-beta/user-guide.md).
|
|
99
|
+
|
|
80
100
|
## Quick Start
|
|
81
101
|
|
|
82
102
|
**Requires Node.js 20.19.0 or higher.**
|
|
@@ -94,9 +114,12 @@ cd your-project
|
|
|
94
114
|
openspec init
|
|
95
115
|
```
|
|
96
116
|
|
|
97
|
-
Now
|
|
117
|
+
Now talk to your AI:
|
|
98
118
|
|
|
99
|
-
|
|
119
|
+
- **Not sure what to build yet?** Start with `/opsx:explore`, a no-stakes thinking partner that reads your code, weighs options, and shapes a plan before anything is written. ([Explore guide](docs/explore.md))
|
|
120
|
+
- **Already know what you want?** Go straight to `/opsx:propose <what-you-want-to-build>`.
|
|
121
|
+
|
|
122
|
+
Both are in the default profile. If you want the expanded workflow (`/opsx:new`, `/opsx:continue`, `/opsx:ff`, `/opsx:verify`, `/opsx:bulk-archive`, `/opsx:onboard`), select it with `openspec config profile` and apply with `openspec update`.
|
|
100
123
|
|
|
101
124
|
> [!NOTE]
|
|
102
125
|
> Not sure if your tool is supported? [View the full list](docs/supported-tools.md) – we support 25+ tools and growing.
|
|
@@ -105,14 +128,24 @@ If you want the expanded workflow (`/opsx:new`, `/opsx:continue`, `/opsx:ff`, `/
|
|
|
105
128
|
|
|
106
129
|
## Docs
|
|
107
130
|
|
|
131
|
+
**Start here:** the **[Documentation Home](docs/README.md)** maps everything. New to OpenSpec? Read [Getting Started](docs/getting-started.md), then [How Commands Work](docs/how-commands-work.md) (where you actually type `/opsx:propose`).
|
|
132
|
+
|
|
108
133
|
→ **[Getting Started](docs/getting-started.md)**: first steps<br>
|
|
134
|
+
→ **[Explore First](docs/explore.md)**: think it through with `/opsx:explore` before you commit<br>
|
|
135
|
+
→ **[How Commands Work](docs/how-commands-work.md)**: where slash commands run vs the CLI<br>
|
|
136
|
+
→ **[Core Concepts at a Glance](docs/overview.md)**: the whole mental model, one page<br>
|
|
137
|
+
→ **[Examples & Recipes](docs/examples.md)**: real changes, start to finish<br>
|
|
109
138
|
→ **[Workflows](docs/workflows.md)**: combos and patterns<br>
|
|
139
|
+
→ **[Existing Projects](docs/existing-projects.md)**: adopt OpenSpec on a brownfield codebase<br>
|
|
140
|
+
→ **[Editing a Change](docs/editing-changes.md)**: update artifacts, go back, reconcile manual edits<br>
|
|
110
141
|
→ **[Commands](docs/commands.md)**: slash commands & skills<br>
|
|
111
142
|
→ **[CLI](docs/cli.md)**: terminal reference<br>
|
|
143
|
+
→ **[Stores](docs/stores-beta/user-guide.md)**: plan in a separate repo, shared across your team (beta)<br>
|
|
112
144
|
→ **[Supported Tools](docs/supported-tools.md)**: tool integrations & install paths<br>
|
|
113
145
|
→ **[Concepts](docs/concepts.md)**: how it all fits<br>
|
|
114
146
|
→ **[Multi-Language](docs/multi-language.md)**: multi-language support<br>
|
|
115
|
-
→ **[Customization](docs/customization.md)**: make it yours
|
|
147
|
+
→ **[Customization](docs/customization.md)**: make it yours<br>
|
|
148
|
+
→ **[FAQ](docs/faq.md)** · **[Troubleshooting](docs/troubleshooting.md)** · **[Glossary](docs/glossary.md)**: quick help
|
|
116
149
|
|
|
117
150
|
|
|
118
151
|
## Community schemas
|
|
@@ -129,7 +162,7 @@ AI coding assistants are powerful but unpredictable when requirements live only
|
|
|
129
162
|
- **Agree before you build** — human and AI align on specs before code gets written
|
|
130
163
|
- **Stay organized** — each change gets its own folder with proposal, specs, design, and tasks
|
|
131
164
|
- **Work fluidly** — update any artifact anytime, no rigid phase gates
|
|
132
|
-
- **Use your tools** — works with
|
|
165
|
+
- **Use your tools** — works with 30+ AI assistants via slash commands
|
|
133
166
|
|
|
134
167
|
### How we compare
|
|
135
168
|
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
declare const program: Command;
|
|
3
|
+
/**
|
|
4
|
+
* Get the full command path for nested commands.
|
|
5
|
+
* For example: 'change show' -> 'change:show'
|
|
6
|
+
*/
|
|
7
|
+
export declare function getCommandPath(command: Command): string;
|
|
3
8
|
export { program };
|
|
4
9
|
export declare function runCli(argv?: string[]): void;
|
|
5
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/cli/index.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { asStatus } from '../commands/shared-output.js';
|
|
2
|
+
import { Command, Option } from 'commander';
|
|
2
3
|
import { createRequire } from 'module';
|
|
3
4
|
import ora from 'ora';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
import { fileURLToPath } from 'url';
|
|
6
7
|
import { promises as fs } from 'fs';
|
|
7
|
-
import { AI_TOOLS
|
|
8
|
+
import { AI_TOOLS } from '../core/config.js';
|
|
8
9
|
import { UpdateCommand } from '../core/update.js';
|
|
9
10
|
import { ListCommand } from '../core/list.js';
|
|
10
11
|
import { ArchiveCommand } from '../core/archive.js';
|
|
11
12
|
import { ViewCommand } from '../core/view.js';
|
|
13
|
+
import { resolveRootForCommand, toRootOutput } from '../core/root-selection.js';
|
|
12
14
|
import { registerSpecCommand } from '../commands/spec.js';
|
|
13
15
|
import { ChangeCommand } from '../commands/change.js';
|
|
14
16
|
import { ValidateCommand } from '../commands/validate.js';
|
|
@@ -17,12 +19,37 @@ import { CompletionCommand } from '../commands/completion.js';
|
|
|
17
19
|
import { FeedbackCommand } from '../commands/feedback.js';
|
|
18
20
|
import { registerConfigCommand } from '../commands/config.js';
|
|
19
21
|
import { registerSchemaCommand } from '../commands/schema.js';
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { statusCommand, instructionsCommand, applyInstructionsCommand, templatesCommand, schemasCommand, newChangeCommand,
|
|
22
|
+
import { registerStoreCommand } from '../commands/store.js';
|
|
23
|
+
import { registerDoctorCommand } from '../commands/doctor.js';
|
|
24
|
+
import { registerContextCommand } from '../commands/context.js';
|
|
25
|
+
import { registerWorksetCommand } from '../commands/workset.js';
|
|
26
|
+
import { statusCommand, instructionsCommand, applyInstructionsCommand, templatesCommand, schemasCommand, newChangeCommand, DEFAULT_SCHEMA, } from '../commands/workflow/index.js';
|
|
25
27
|
import { maybeShowTelemetryNotice, trackCommand, shutdown } from '../telemetry/index.js';
|
|
28
|
+
import { COMMON_FLAGS } from '../core/completions/shared-flags.js';
|
|
29
|
+
const STORE_OPTION_DESCRIPTION = COMMON_FLAGS.store.description;
|
|
30
|
+
// Deliberate rejection path: --store-path stays registered (hidden) so the
|
|
31
|
+
// resolver can explain that registering the path is the supported route,
|
|
32
|
+
// instead of Commander emitting a generic unknown-option error (or, for
|
|
33
|
+
// `show`, silently ignoring it via allowUnknownOption).
|
|
34
|
+
function hiddenStorePathOption() {
|
|
35
|
+
return new Option('--store-path <path>', 'Not supported; register the path with "openspec store register <path>" and use --store <id>').hideHelp();
|
|
36
|
+
}
|
|
37
|
+
function failWithError(error, json) {
|
|
38
|
+
// The agent contract: every --json failure leaves exactly one JSON
|
|
39
|
+
// document on stdout (the command's null-shape plus a status array).
|
|
40
|
+
if (json?.enabled) {
|
|
41
|
+
console.log(JSON.stringify({ ...(json.payload ?? {}), status: [asStatus(error, json.fallbackCode ?? 'command_error')] }, null, 2));
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
ora().fail(`Error: ${error.message}`);
|
|
46
|
+
// Resolution and store errors carry a pasteable fix - never drop it.
|
|
47
|
+
const fix = error.diagnostic?.fix;
|
|
48
|
+
if (fix) {
|
|
49
|
+
console.error(`Fix: ${fix}`);
|
|
50
|
+
}
|
|
51
|
+
process.exitCode = process.exitCode ?? 1;
|
|
52
|
+
}
|
|
26
53
|
const program = new Command();
|
|
27
54
|
const require = createRequire(import.meta.url);
|
|
28
55
|
const { version } = require('../../package.json');
|
|
@@ -30,7 +57,7 @@ const { version } = require('../../package.json');
|
|
|
30
57
|
* Get the full command path for nested commands.
|
|
31
58
|
* For example: 'change show' -> 'change:show'
|
|
32
59
|
*/
|
|
33
|
-
function getCommandPath(command) {
|
|
60
|
+
export function getCommandPath(command) {
|
|
34
61
|
const names = [];
|
|
35
62
|
let current = command;
|
|
36
63
|
while (current) {
|
|
@@ -70,21 +97,6 @@ program.hook('postAction', async () => {
|
|
|
70
97
|
});
|
|
71
98
|
const availableToolIds = AI_TOOLS.filter((tool) => tool.skillsDir).map((tool) => tool.value);
|
|
72
99
|
const toolsOptionDescription = `Configure AI tools non-interactively. Use "all", "none", or a comma-separated list of: ${availableToolIds.join(', ')}`;
|
|
73
|
-
async function hasRepoLocalOpenSpecProject(projectPath) {
|
|
74
|
-
try {
|
|
75
|
-
const stats = await fs.stat(path.join(projectPath, OPENSPEC_DIR_NAME));
|
|
76
|
-
return stats.isDirectory();
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
const code = typeof error === 'object' && error !== null && 'code' in error
|
|
80
|
-
? error.code
|
|
81
|
-
: undefined;
|
|
82
|
-
if (code !== 'ENOENT' && code !== 'ENOTDIR') {
|
|
83
|
-
throw error;
|
|
84
|
-
}
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
100
|
program
|
|
89
101
|
.command('init [path]')
|
|
90
102
|
.description('Initialize OpenSpec in your project')
|
|
@@ -122,8 +134,7 @@ program
|
|
|
122
134
|
await initCommand.execute(targetPath);
|
|
123
135
|
}
|
|
124
136
|
catch (error) {
|
|
125
|
-
|
|
126
|
-
ora().fail(`Error: ${error.message}`);
|
|
137
|
+
failWithError(error);
|
|
127
138
|
process.exit(1);
|
|
128
139
|
}
|
|
129
140
|
});
|
|
@@ -144,8 +155,7 @@ program
|
|
|
144
155
|
await initCommand.execute('.');
|
|
145
156
|
}
|
|
146
157
|
catch (error) {
|
|
147
|
-
|
|
148
|
-
ora().fail(`Error: ${error.message}`);
|
|
158
|
+
failWithError(error);
|
|
149
159
|
process.exit(1);
|
|
150
160
|
}
|
|
151
161
|
});
|
|
@@ -155,21 +165,11 @@ program
|
|
|
155
165
|
.option('--force', 'Force update even when tools are up to date')
|
|
156
166
|
.action(async (targetPath = '.', options) => {
|
|
157
167
|
try {
|
|
158
|
-
const resolvedPath = path.resolve(targetPath);
|
|
159
168
|
const updateCommand = new UpdateCommand({ force: options?.force });
|
|
160
|
-
|
|
161
|
-
await updateCommand.execute(resolvedPath);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
const workspaceRoot = await findWorkspaceRoot(resolvedPath);
|
|
165
|
-
if (workspaceRoot) {
|
|
166
|
-
throw new Error('OpenSpec workspace detected. Run `openspec workspace update` to refresh workspace-local guidance and skills.');
|
|
167
|
-
}
|
|
168
|
-
await updateCommand.execute(resolvedPath);
|
|
169
|
+
await updateCommand.execute(targetPath);
|
|
169
170
|
}
|
|
170
171
|
catch (error) {
|
|
171
|
-
|
|
172
|
-
ora().fail(`Error: ${error.message}`);
|
|
172
|
+
failWithError(error);
|
|
173
173
|
process.exit(1);
|
|
174
174
|
}
|
|
175
175
|
});
|
|
@@ -180,16 +180,32 @@ program
|
|
|
180
180
|
.option('--changes', 'List changes explicitly (default)')
|
|
181
181
|
.option('--sort <order>', 'Sort order: "recent" (default) or "name"', 'recent')
|
|
182
182
|
.option('--json', 'Output as JSON (for programmatic use)')
|
|
183
|
+
.option('--store <id>', STORE_OPTION_DESCRIPTION)
|
|
184
|
+
.addOption(hiddenStorePathOption())
|
|
183
185
|
.action(async (options) => {
|
|
184
186
|
try {
|
|
187
|
+
const root = await resolveRootForCommand(options ?? {}, {
|
|
188
|
+
json: options?.json,
|
|
189
|
+
failurePayload: options?.specs ? { specs: [], root: null } : { changes: [], root: null },
|
|
190
|
+
});
|
|
191
|
+
if (!root) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
185
194
|
const listCommand = new ListCommand();
|
|
186
195
|
const mode = options?.specs ? 'specs' : 'changes';
|
|
187
196
|
const sort = options?.sort === 'name' ? 'name' : 'recent';
|
|
188
|
-
await listCommand.execute(
|
|
197
|
+
await listCommand.execute(root.path, mode, {
|
|
198
|
+
sort,
|
|
199
|
+
json: options?.json,
|
|
200
|
+
...(options?.json ? { root: toRootOutput(root) } : {}),
|
|
201
|
+
});
|
|
189
202
|
}
|
|
190
203
|
catch (error) {
|
|
191
|
-
|
|
192
|
-
|
|
204
|
+
failWithError(error, {
|
|
205
|
+
enabled: options?.json,
|
|
206
|
+
payload: options?.specs ? { specs: [], root: null } : { changes: [], root: null },
|
|
207
|
+
fallbackCode: 'list_error',
|
|
208
|
+
});
|
|
193
209
|
process.exit(1);
|
|
194
210
|
}
|
|
195
211
|
});
|
|
@@ -202,8 +218,7 @@ program
|
|
|
202
218
|
await viewCommand.execute('.');
|
|
203
219
|
}
|
|
204
220
|
catch (error) {
|
|
205
|
-
|
|
206
|
-
ora().fail(`Error: ${error.message}`);
|
|
221
|
+
failWithError(error);
|
|
207
222
|
process.exit(1);
|
|
208
223
|
}
|
|
209
224
|
});
|
|
@@ -273,23 +288,26 @@ program
|
|
|
273
288
|
.option('-y, --yes', 'Skip confirmation prompts')
|
|
274
289
|
.option('--skip-specs', 'Skip spec update operations (useful for infrastructure, tooling, or doc-only changes)')
|
|
275
290
|
.option('--no-validate', 'Skip validation (not recommended, requires confirmation)')
|
|
291
|
+
.option('--json', 'Output as JSON (non-interactive)')
|
|
292
|
+
.option('--store <id>', STORE_OPTION_DESCRIPTION)
|
|
293
|
+
.addOption(hiddenStorePathOption())
|
|
276
294
|
.action(async (changeName, options) => {
|
|
277
295
|
try {
|
|
278
296
|
const archiveCommand = new ArchiveCommand();
|
|
279
297
|
await archiveCommand.execute(changeName, options);
|
|
280
298
|
}
|
|
281
299
|
catch (error) {
|
|
282
|
-
|
|
283
|
-
ora().fail(`Error: ${error.message}`);
|
|
300
|
+
failWithError(error);
|
|
284
301
|
process.exit(1);
|
|
285
302
|
}
|
|
286
303
|
});
|
|
287
304
|
registerSpecCommand(program);
|
|
288
305
|
registerConfigCommand(program);
|
|
289
306
|
registerSchemaCommand(program);
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
307
|
+
registerStoreCommand(program);
|
|
308
|
+
registerDoctorCommand(program);
|
|
309
|
+
registerContextCommand(program);
|
|
310
|
+
registerWorksetCommand(program);
|
|
293
311
|
// Top-level validate command
|
|
294
312
|
program
|
|
295
313
|
.command('validate [item-name]')
|
|
@@ -302,14 +320,15 @@ program
|
|
|
302
320
|
.option('--json', 'Output validation results as JSON')
|
|
303
321
|
.option('--concurrency <n>', 'Max concurrent validations (defaults to env OPENSPEC_CONCURRENCY or 6)')
|
|
304
322
|
.option('--no-interactive', 'Disable interactive prompts')
|
|
323
|
+
.option('--store <id>', STORE_OPTION_DESCRIPTION)
|
|
324
|
+
.addOption(hiddenStorePathOption())
|
|
305
325
|
.action(async (itemName, options) => {
|
|
306
326
|
try {
|
|
307
327
|
const validateCommand = new ValidateCommand();
|
|
308
328
|
await validateCommand.execute(itemName, options);
|
|
309
329
|
}
|
|
310
330
|
catch (error) {
|
|
311
|
-
|
|
312
|
-
ora().fail(`Error: ${error.message}`);
|
|
331
|
+
failWithError(error, { enabled: options?.json, fallbackCode: 'validate_error' });
|
|
313
332
|
process.exit(1);
|
|
314
333
|
}
|
|
315
334
|
});
|
|
@@ -327,6 +346,10 @@ program
|
|
|
327
346
|
.option('--requirements', 'JSON only: Show only requirements (exclude scenarios)')
|
|
328
347
|
.option('--no-scenarios', 'JSON only: Exclude scenario content')
|
|
329
348
|
.option('-r, --requirement <id>', 'JSON only: Show specific requirement by ID (1-based)')
|
|
349
|
+
.option('--store <id>', STORE_OPTION_DESCRIPTION)
|
|
350
|
+
// Explicit registration required: allowUnknownOption would otherwise
|
|
351
|
+
// silently swallow --store-path instead of rejecting it deliberately.
|
|
352
|
+
.addOption(hiddenStorePathOption())
|
|
330
353
|
// allow unknown options to pass-through to underlying command implementation
|
|
331
354
|
.allowUnknownOption(true)
|
|
332
355
|
.action(async (itemName, options) => {
|
|
@@ -335,8 +358,7 @@ program
|
|
|
335
358
|
await showCommand.execute(itemName, options ?? {});
|
|
336
359
|
}
|
|
337
360
|
catch (error) {
|
|
338
|
-
|
|
339
|
-
ora().fail(`Error: ${error.message}`);
|
|
361
|
+
failWithError(error, { enabled: options?.json, fallbackCode: 'show_error' });
|
|
340
362
|
process.exit(1);
|
|
341
363
|
}
|
|
342
364
|
});
|
|
@@ -351,8 +373,7 @@ program
|
|
|
351
373
|
await feedbackCommand.execute(message, options);
|
|
352
374
|
}
|
|
353
375
|
catch (error) {
|
|
354
|
-
|
|
355
|
-
ora().fail(`Error: ${error.message}`);
|
|
376
|
+
failWithError(error);
|
|
356
377
|
process.exit(1);
|
|
357
378
|
}
|
|
358
379
|
});
|
|
@@ -369,8 +390,7 @@ completionCmd
|
|
|
369
390
|
await completionCommand.generate({ shell });
|
|
370
391
|
}
|
|
371
392
|
catch (error) {
|
|
372
|
-
|
|
373
|
-
ora().fail(`Error: ${error.message}`);
|
|
393
|
+
failWithError(error);
|
|
374
394
|
process.exit(1);
|
|
375
395
|
}
|
|
376
396
|
});
|
|
@@ -384,8 +404,7 @@ completionCmd
|
|
|
384
404
|
await completionCommand.install({ shell, verbose: options?.verbose });
|
|
385
405
|
}
|
|
386
406
|
catch (error) {
|
|
387
|
-
|
|
388
|
-
ora().fail(`Error: ${error.message}`);
|
|
407
|
+
failWithError(error);
|
|
389
408
|
process.exit(1);
|
|
390
409
|
}
|
|
391
410
|
});
|
|
@@ -399,8 +418,7 @@ completionCmd
|
|
|
399
418
|
await completionCommand.uninstall({ shell, yes: options?.yes });
|
|
400
419
|
}
|
|
401
420
|
catch (error) {
|
|
402
|
-
|
|
403
|
-
ora().fail(`Error: ${error.message}`);
|
|
421
|
+
failWithError(error);
|
|
404
422
|
process.exit(1);
|
|
405
423
|
}
|
|
406
424
|
});
|
|
@@ -428,13 +446,14 @@ program
|
|
|
428
446
|
.option('--change <id>', 'Change name to show status for')
|
|
429
447
|
.option('--schema <name>', 'Schema override (auto-detected from config.yaml)')
|
|
430
448
|
.option('--json', 'Output as JSON')
|
|
449
|
+
.option('--store <id>', STORE_OPTION_DESCRIPTION)
|
|
450
|
+
.addOption(hiddenStorePathOption())
|
|
431
451
|
.action(async (options) => {
|
|
432
452
|
try {
|
|
433
453
|
await statusCommand(options);
|
|
434
454
|
}
|
|
435
455
|
catch (error) {
|
|
436
|
-
|
|
437
|
-
ora().fail(`Error: ${error.message}`);
|
|
456
|
+
failWithError(error, { enabled: options.json, fallbackCode: 'change_error' });
|
|
438
457
|
process.exit(1);
|
|
439
458
|
}
|
|
440
459
|
});
|
|
@@ -445,6 +464,8 @@ program
|
|
|
445
464
|
.option('--change <id>', 'Change name')
|
|
446
465
|
.option('--schema <name>', 'Schema override (auto-detected from config.yaml)')
|
|
447
466
|
.option('--json', 'Output as JSON')
|
|
467
|
+
.option('--store <id>', STORE_OPTION_DESCRIPTION)
|
|
468
|
+
.addOption(hiddenStorePathOption())
|
|
448
469
|
.action(async (artifactId, options) => {
|
|
449
470
|
try {
|
|
450
471
|
// Special case: "apply" is not an artifact, but a command to get apply instructions
|
|
@@ -456,8 +477,7 @@ program
|
|
|
456
477
|
}
|
|
457
478
|
}
|
|
458
479
|
catch (error) {
|
|
459
|
-
|
|
460
|
-
ora().fail(`Error: ${error.message}`);
|
|
480
|
+
failWithError(error, { enabled: options.json, fallbackCode: 'change_error' });
|
|
461
481
|
process.exit(1);
|
|
462
482
|
}
|
|
463
483
|
});
|
|
@@ -472,8 +492,7 @@ program
|
|
|
472
492
|
await templatesCommand(options);
|
|
473
493
|
}
|
|
474
494
|
catch (error) {
|
|
475
|
-
|
|
476
|
-
ora().fail(`Error: ${error.message}`);
|
|
495
|
+
failWithError(error);
|
|
477
496
|
process.exit(1);
|
|
478
497
|
}
|
|
479
498
|
});
|
|
@@ -487,8 +506,7 @@ program
|
|
|
487
506
|
await schemasCommand(options);
|
|
488
507
|
}
|
|
489
508
|
catch (error) {
|
|
490
|
-
|
|
491
|
-
ora().fail(`Error: ${error.message}`);
|
|
509
|
+
failWithError(error);
|
|
492
510
|
process.exit(1);
|
|
493
511
|
}
|
|
494
512
|
});
|
|
@@ -498,39 +516,21 @@ newCmd
|
|
|
498
516
|
.command('change <name>')
|
|
499
517
|
.description('Create a new change directory')
|
|
500
518
|
.option('--description <text>', 'Description to add to README.md')
|
|
501
|
-
.option('--goal <text>', '
|
|
502
|
-
.option('--areas <names>', 'Comma-separated affected workspace link names')
|
|
503
|
-
.option('--initiative <id>', 'Link the repo-local change to an initiative')
|
|
504
|
-
.option('--store <id>', 'Context store id for --initiative')
|
|
505
|
-
.option('--store-path <path>', 'Existing local context store root for --initiative')
|
|
519
|
+
.option('--goal <text>', 'Optional goal metadata to store with the change')
|
|
506
520
|
.option('--schema <name>', `Workflow schema to use (default: ${DEFAULT_SCHEMA})`)
|
|
507
521
|
.option('--json', 'Output as JSON')
|
|
522
|
+
.option('--store <id>', STORE_OPTION_DESCRIPTION)
|
|
523
|
+
.addOption(hiddenStorePathOption())
|
|
524
|
+
// Removed options kept registered (hidden) so users get a deliberate
|
|
525
|
+
// explanation instead of a generic unknown-option error.
|
|
526
|
+
.addOption(new Option('--initiative <id>', 'No longer supported').hideHelp())
|
|
527
|
+
.addOption(new Option('--areas <names>', 'No longer supported').hideHelp())
|
|
508
528
|
.action(async (name, options) => {
|
|
509
529
|
try {
|
|
510
530
|
await newChangeCommand(name, options);
|
|
511
531
|
}
|
|
512
532
|
catch (error) {
|
|
513
|
-
|
|
514
|
-
ora().fail(`Error: ${error.message}`);
|
|
515
|
-
process.exit(1);
|
|
516
|
-
}
|
|
517
|
-
});
|
|
518
|
-
// Set command group
|
|
519
|
-
const setCmd = program.command('set').description('Set checked-in OpenSpec metadata');
|
|
520
|
-
setCmd
|
|
521
|
-
.command('change <name>')
|
|
522
|
-
.description('Set repo-local change metadata')
|
|
523
|
-
.option('--initiative <id>', 'Link the repo-local change to an initiative')
|
|
524
|
-
.option('--store <id>', 'Context store id for --initiative')
|
|
525
|
-
.option('--store-path <path>', 'Existing local context store root for --initiative')
|
|
526
|
-
.option('--json', 'Output as JSON')
|
|
527
|
-
.action(async (name, options) => {
|
|
528
|
-
try {
|
|
529
|
-
await setChangeCommand(name, options);
|
|
530
|
-
}
|
|
531
|
-
catch (error) {
|
|
532
|
-
console.log();
|
|
533
|
-
ora().fail(`Error: ${error.message}`);
|
|
533
|
+
failWithError(error);
|
|
534
534
|
process.exit(1);
|
|
535
535
|
}
|
|
536
536
|
});
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import type { RootOutput } from '../core/root-selection.js';
|
|
1
2
|
export declare class ChangeCommand {
|
|
2
3
|
private converter;
|
|
3
|
-
|
|
4
|
+
private rootPath?;
|
|
5
|
+
constructor(rootPath?: string);
|
|
6
|
+
private getChangesPath;
|
|
4
7
|
/**
|
|
5
8
|
* Show a change proposal.
|
|
6
9
|
* - Text mode: raw markdown passthrough (no filters)
|
|
@@ -12,6 +15,7 @@ export declare class ChangeCommand {
|
|
|
12
15
|
requirementsOnly?: boolean;
|
|
13
16
|
deltasOnly?: boolean;
|
|
14
17
|
noInteractive?: boolean;
|
|
18
|
+
rootOutput?: RootOutput;
|
|
15
19
|
}): Promise<void>;
|
|
16
20
|
/**
|
|
17
21
|
* List active changes.
|
|
@@ -29,7 +33,6 @@ export declare class ChangeCommand {
|
|
|
29
33
|
}): Promise<void>;
|
|
30
34
|
private getActiveChanges;
|
|
31
35
|
private extractTitle;
|
|
32
|
-
private countTasks;
|
|
33
36
|
private printNextSteps;
|
|
34
37
|
}
|
|
35
38
|
//# sourceMappingURL=change.d.ts.map
|
package/dist/commands/change.js
CHANGED
|
@@ -5,14 +5,20 @@ import { Validator } from '../core/validation/validator.js';
|
|
|
5
5
|
import { ChangeParser } from '../core/parsers/change-parser.js';
|
|
6
6
|
import { isInteractive } from '../utils/interactive.js';
|
|
7
7
|
import { getActiveChangeIds } from '../utils/item-discovery.js';
|
|
8
|
+
import { getTaskProgressForChange } from '../utils/task-progress.js';
|
|
8
9
|
// Constants for better maintainability
|
|
9
10
|
const ARCHIVE_DIR = 'archive';
|
|
10
|
-
const TASK_PATTERN = /^[-*]\s+\[[\sx]\]/i;
|
|
11
|
-
const COMPLETED_TASK_PATTERN = /^[-*]\s+\[x\]/i;
|
|
12
11
|
export class ChangeCommand {
|
|
13
12
|
converter;
|
|
14
|
-
|
|
13
|
+
rootPath;
|
|
14
|
+
// rootPath is set only by root-aware callers (top-level `show`); the
|
|
15
|
+
// deprecated noun-form commands stay cwd-based.
|
|
16
|
+
constructor(rootPath) {
|
|
15
17
|
this.converter = new JsonConverter();
|
|
18
|
+
this.rootPath = rootPath;
|
|
19
|
+
}
|
|
20
|
+
getChangesPath() {
|
|
21
|
+
return path.join(this.rootPath ?? process.cwd(), 'openspec', 'changes');
|
|
16
22
|
}
|
|
17
23
|
/**
|
|
18
24
|
* Show a change proposal.
|
|
@@ -21,7 +27,7 @@ export class ChangeCommand {
|
|
|
21
27
|
* Note: --requirements-only is deprecated alias for --deltas-only
|
|
22
28
|
*/
|
|
23
29
|
async show(changeName, options) {
|
|
24
|
-
const changesPath =
|
|
30
|
+
const changesPath = this.getChangesPath();
|
|
25
31
|
if (!changeName) {
|
|
26
32
|
const canPrompt = isInteractive(options);
|
|
27
33
|
const changes = await this.getActiveChanges(changesPath);
|
|
@@ -62,19 +68,14 @@ export class ChangeCommand {
|
|
|
62
68
|
const title = this.extractTitle(contentForTitle, changeName);
|
|
63
69
|
const id = parsed.name;
|
|
64
70
|
const deltas = parsed.deltas || [];
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
deltaCount: deltas.length,
|
|
74
|
-
deltas,
|
|
75
|
-
};
|
|
76
|
-
console.log(JSON.stringify(output, null, 2));
|
|
77
|
-
}
|
|
71
|
+
const output = {
|
|
72
|
+
id,
|
|
73
|
+
title,
|
|
74
|
+
deltaCount: deltas.length,
|
|
75
|
+
deltas,
|
|
76
|
+
...(options.rootOutput ? { root: options.rootOutput } : {}),
|
|
77
|
+
};
|
|
78
|
+
console.log(JSON.stringify(output, null, 2));
|
|
78
79
|
}
|
|
79
80
|
else {
|
|
80
81
|
const content = await fs.readFile(proposalPath, 'utf-8');
|
|
@@ -92,23 +93,14 @@ export class ChangeCommand {
|
|
|
92
93
|
if (options?.json) {
|
|
93
94
|
const changeDetails = await Promise.all(changes.map(async (changeName) => {
|
|
94
95
|
const proposalPath = path.join(changesPath, changeName, 'proposal.md');
|
|
95
|
-
const tasksPath = path.join(changesPath, changeName, 'tasks.md');
|
|
96
96
|
try {
|
|
97
97
|
const content = await fs.readFile(proposalPath, 'utf-8');
|
|
98
98
|
const changeDir = path.join(changesPath, changeName);
|
|
99
99
|
const parser = new ChangeParser(content, changeDir);
|
|
100
100
|
const change = await parser.parseChangeWithDeltas(changeName);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
taskStatus = this.countTasks(tasksContent);
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
// Tasks file may not exist, which is okay
|
|
108
|
-
if (process.env.DEBUG) {
|
|
109
|
-
console.error(`Failed to read tasks file at ${tasksPath}:`, error);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
101
|
+
// Resolve task progress through the shared tracked-tasks helper so
|
|
102
|
+
// this deprecated noun-form list cannot re-fork the resolution (#1202).
|
|
103
|
+
const taskStatus = await getTaskProgressForChange(changesPath, changeName, process.cwd());
|
|
112
104
|
return {
|
|
113
105
|
id: changeName,
|
|
114
106
|
title: this.extractTitle(content, changeName),
|
|
@@ -142,21 +134,11 @@ export class ChangeCommand {
|
|
|
142
134
|
// Long format: id: title and minimal counts
|
|
143
135
|
for (const changeName of sorted) {
|
|
144
136
|
const proposalPath = path.join(changesPath, changeName, 'proposal.md');
|
|
145
|
-
const tasksPath = path.join(changesPath, changeName, 'tasks.md');
|
|
146
137
|
try {
|
|
147
138
|
const content = await fs.readFile(proposalPath, 'utf-8');
|
|
148
139
|
const title = this.extractTitle(content, changeName);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const tasksContent = await fs.readFile(tasksPath, 'utf-8');
|
|
152
|
-
const { total, completed } = this.countTasks(tasksContent);
|
|
153
|
-
taskStatusText = ` [tasks ${completed}/${total}]`;
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
if (process.env.DEBUG) {
|
|
157
|
-
console.error(`Failed to read tasks file at ${tasksPath}:`, error);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
140
|
+
const { total, completed } = await getTaskProgressForChange(changesPath, changeName, process.cwd());
|
|
141
|
+
const taskStatusText = total > 0 ? ` [tasks ${completed}/${total}]` : '';
|
|
160
142
|
const changeDir = path.join(changesPath, changeName);
|
|
161
143
|
const parser = new ChangeParser(await fs.readFile(proposalPath, 'utf-8'), changeDir);
|
|
162
144
|
const change = await parser.parseChangeWithDeltas(changeName);
|
|
@@ -251,20 +233,6 @@ export class ChangeCommand {
|
|
|
251
233
|
const match = content.match(/^#\s+(?:Change:\s+)?(.+)$/im);
|
|
252
234
|
return match ? match[1].trim() : changeName;
|
|
253
235
|
}
|
|
254
|
-
countTasks(content) {
|
|
255
|
-
const lines = content.split('\n');
|
|
256
|
-
let total = 0;
|
|
257
|
-
let completed = 0;
|
|
258
|
-
for (const line of lines) {
|
|
259
|
-
if (line.match(TASK_PATTERN)) {
|
|
260
|
-
total++;
|
|
261
|
-
if (line.match(COMPLETED_TASK_PATTERN)) {
|
|
262
|
-
completed++;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return { total, completed };
|
|
267
|
-
}
|
|
268
236
|
printNextSteps() {
|
|
269
237
|
const bullets = [];
|
|
270
238
|
bullets.push('- Ensure change has deltas in specs/: use headers ## ADDED/MODIFIED/REMOVED/RENAMED Requirements');
|