@rsaglobaltech/specgate 0.20.0 → 0.22.0
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/CHANGELOG.md +208 -0
- package/README.md +31 -31
- package/bin/specgate.ts +25 -19
- package/dist/bin/specgate.js +15 -10
- package/dist/bin/specgate.js.map +1 -1
- package/dist/packages/core/src/application/AddRequirementUseCase.js +11 -1
- package/dist/packages/core/src/application/AddRequirementUseCase.js.map +1 -1
- package/dist/packages/core/src/application/ArchiveChangeUseCase.js +7 -2
- package/dist/packages/core/src/application/ArchiveChangeUseCase.js.map +1 -1
- package/dist/packages/core/src/application/ValidateProjectUseCase.js +9 -3
- package/dist/packages/core/src/application/ValidateProjectUseCase.js.map +1 -1
- package/dist/packages/core/src/domain/ArchivePlan.js.map +1 -1
- package/dist/packages/core/src/domain/BriefCards.js +320 -0
- package/dist/packages/core/src/domain/BriefCards.js.map +1 -0
- package/dist/packages/core/src/domain/ChangeTemplates.js +19 -0
- package/dist/packages/core/src/domain/ChangeTemplates.js.map +1 -1
- package/dist/packages/core/src/domain/DeltaSpec.js +14 -7
- package/dist/packages/core/src/domain/DeltaSpec.js.map +1 -1
- package/dist/packages/core/src/domain/DerivedMatrix.js +2 -2
- package/dist/packages/core/src/domain/DerivedMatrix.js.map +1 -1
- package/dist/packages/core/src/domain/Diagnostic.js.map +1 -1
- package/dist/packages/core/src/domain/DoneVerification.js +3 -3
- package/dist/packages/core/src/domain/DoneVerification.js.map +1 -1
- package/dist/packages/core/src/domain/DraftChecklist.js +6 -2
- package/dist/packages/core/src/domain/DraftChecklist.js.map +1 -1
- package/dist/packages/core/src/domain/GherkinQuality.js +5 -2
- package/dist/packages/core/src/domain/GherkinQuality.js.map +1 -1
- package/dist/packages/core/src/domain/HarnessEvents.js +129 -0
- package/dist/packages/core/src/domain/HarnessEvents.js.map +1 -0
- package/dist/packages/core/src/domain/HarnessRun.js.map +1 -1
- package/dist/packages/core/src/domain/Language.js +5 -2
- package/dist/packages/core/src/domain/Language.js.map +1 -1
- package/dist/packages/core/src/domain/PackContribution.js.map +1 -1
- package/dist/packages/core/src/domain/PackDelta.js +5 -2
- package/dist/packages/core/src/domain/PackDelta.js.map +1 -1
- package/dist/packages/core/src/domain/PackSpec.js +16 -2
- package/dist/packages/core/src/domain/PackSpec.js.map +1 -1
- package/dist/packages/core/src/domain/QuotedText.js +41 -0
- package/dist/packages/core/src/domain/QuotedText.js.map +1 -0
- package/dist/packages/core/src/domain/RequirementPlan.js.map +1 -1
- package/dist/packages/core/src/domain/SpecParser.js +2 -9
- package/dist/packages/core/src/domain/SpecParser.js.map +1 -1
- package/dist/packages/core/src/domain/TicketText.js +337 -0
- package/dist/packages/core/src/domain/TicketText.js.map +1 -0
- package/dist/packages/core/src/domain/TraceabilityFormat.js.map +1 -1
- package/dist/packages/core/src/domain/TraceabilityMatrix.js +3 -3
- package/dist/packages/core/src/domain/TraceabilityMatrix.js.map +1 -1
- package/dist/packages/core/src/domain/YamlLite.js +3 -0
- package/dist/packages/core/src/domain/YamlLite.js.map +1 -1
- package/dist/packages/core/src/infrastructure/ChangeWorkspace.js +7 -3
- package/dist/packages/core/src/infrastructure/ChangeWorkspace.js.map +1 -1
- package/dist/packages/core/src/infrastructure/ConsoleReporter.js.map +1 -1
- package/dist/packages/core/src/infrastructure/DiskLanguageRepository.js +1 -0
- package/dist/packages/core/src/infrastructure/DiskLanguageRepository.js.map +1 -1
- package/dist/packages/core/src/infrastructure/DiskPackDeltaRepository.js +3 -1
- package/dist/packages/core/src/infrastructure/DiskPackDeltaRepository.js.map +1 -1
- package/dist/packages/core/src/infrastructure/DiskPackRepository.js +0 -2
- package/dist/packages/core/src/infrastructure/DiskPackRepository.js.map +1 -1
- package/dist/packages/core/src/infrastructure/DiskRequirementGraphRepository.js.map +1 -1
- package/dist/packages/core/src/infrastructure/GitContributionStager.js +0 -4
- package/dist/packages/core/src/infrastructure/GitContributionStager.js.map +1 -1
- package/dist/packages/core/src/infrastructure/GitMergeDriver.js +0 -3
- package/dist/packages/core/src/infrastructure/GitMergeDriver.js.map +1 -1
- package/dist/packages/core/src/infrastructure/HarnessConfigFile.js +7 -13
- package/dist/packages/core/src/infrastructure/HarnessConfigFile.js.map +1 -1
- package/dist/packages/core/src/infrastructure/IdsAcrossBranches.js +48 -0
- package/dist/packages/core/src/infrastructure/IdsAcrossBranches.js.map +1 -0
- package/dist/packages/core/src/infrastructure/PackChangeDeposit.js.map +1 -1
- package/dist/packages/core/src/infrastructure/RemotePackResolver.js +0 -7
- package/dist/packages/core/src/infrastructure/RemotePackResolver.js.map +1 -1
- package/dist/packages/core/src/infrastructure/SpecopsConfigFile.js.map +1 -1
- package/dist/packages/core/test/unit/declared-artifacts.test.js +6 -2
- package/dist/packages/core/test/unit/declared-artifacts.test.js.map +1 -1
- package/dist/packages/core/test/unit/derived-matrix.test.js +3 -0
- package/dist/packages/core/test/unit/derived-matrix.test.js.map +1 -1
- package/dist/packages/core/test/unit/done-verification.test.js +3 -0
- package/dist/packages/core/test/unit/done-verification.test.js.map +1 -1
- package/dist/packages/core/test/unit/matrix-dependencies.test.js.map +1 -1
- package/dist/packages/core/test/unit/reconciliation.test.js.map +1 -1
- package/dist/packages/core/test/unit/requirement-readiness.test.js +2 -2
- package/dist/packages/core/test/unit/requirement-readiness.test.js.map +1 -1
- package/dist/packages/core/test/unit/resume-state.test.js +4 -1
- package/dist/packages/core/test/unit/resume-state.test.js.map +1 -1
- package/dist/packages/core/test/unit/run-budget.test.js +3 -0
- package/dist/packages/core/test/unit/run-budget.test.js.map +1 -1
- package/dist/packages/core/test/unit/untouched-delta.test.js +52 -0
- package/dist/packages/core/test/unit/untouched-delta.test.js.map +1 -0
- package/dist/packages/core/test/unit/validate-project.test.js +6 -2
- package/dist/packages/core/test/unit/validate-project.test.js.map +1 -1
- package/dist/packages/mcp-spec-driven/src/server.js +4 -3
- package/dist/packages/mcp-spec-driven/src/server.js.map +1 -1
- package/dist/packages/mcp-spec-driven/src/tools.js.map +1 -1
- package/dist/scripts/agents/commands.js +52 -38
- package/dist/scripts/agents/commands.js.map +1 -1
- package/dist/scripts/agents/contract-file.js +44 -0
- package/dist/scripts/agents/contract-file.js.map +1 -1
- package/dist/scripts/agents/detect.js.map +1 -1
- package/dist/scripts/agents/init.js.map +1 -1
- package/dist/scripts/alm/cli.js +16 -5
- package/dist/scripts/alm/cli.js.map +1 -1
- package/dist/scripts/alm/core.js +1 -0
- package/dist/scripts/alm/core.js.map +1 -1
- package/dist/scripts/alm/port.js +1 -1
- package/dist/scripts/alm/port.js.map +1 -1
- package/dist/scripts/alm/providers/http-client.js +46 -3
- package/dist/scripts/alm/providers/http-client.js.map +1 -1
- package/dist/scripts/alm/providers/jira.js +108 -32
- package/dist/scripts/alm/providers/jira.js.map +1 -1
- package/dist/scripts/apply.js +9 -0
- package/dist/scripts/apply.js.map +1 -0
- package/dist/scripts/archive.js +9 -0
- package/dist/scripts/archive.js.map +1 -0
- package/dist/scripts/change/archive.js.map +1 -1
- package/dist/scripts/change/artifacts.js.map +1 -1
- package/dist/scripts/change/delta.js +0 -8
- package/dist/scripts/change/delta.js.map +1 -1
- package/dist/scripts/change/instructions.js +9 -11
- package/dist/scripts/change/instructions.js.map +1 -1
- package/dist/scripts/cli/commands/alm/PrCommand.js +198 -0
- package/dist/scripts/cli/commands/alm/PrCommand.js.map +1 -0
- package/dist/scripts/cli/commands/alm/PullCommand.js +4 -2
- package/dist/scripts/cli/commands/alm/PullCommand.js.map +1 -1
- package/dist/scripts/cli/commands/change/ChangeCommand.js +119 -19
- package/dist/scripts/cli/commands/change/ChangeCommand.js.map +1 -1
- package/dist/scripts/cli/commands/change/TicketCommand.js +657 -0
- package/dist/scripts/cli/commands/change/TicketCommand.js.map +1 -0
- package/dist/scripts/cli/commands/config/ConfigSetCommand.js +1 -1
- package/dist/scripts/cli/commands/config/ConfigSetCommand.js.map +1 -1
- package/dist/scripts/cli/commands/harness/RunCommand.js +225 -35
- package/dist/scripts/cli/commands/harness/RunCommand.js.map +1 -1
- package/dist/scripts/cli/commands/mcp/McpInstallCommand.js +1 -0
- package/dist/scripts/cli/commands/mcp/McpInstallCommand.js.map +1 -1
- package/dist/scripts/cli/commands/pack/BundlePackCommand.js +2 -1
- package/dist/scripts/cli/commands/pack/BundlePackCommand.js.map +1 -1
- package/dist/scripts/cli/commands/pack/ExpandDomainPackCommand.js +32 -17
- package/dist/scripts/cli/commands/pack/ExpandDomainPackCommand.js.map +1 -1
- package/dist/scripts/cli/commands/pack/InferPackCommand.js +8 -5
- package/dist/scripts/cli/commands/pack/InferPackCommand.js.map +1 -1
- package/dist/scripts/cli/commands/pack/LintPackCommand.js +36 -44
- package/dist/scripts/cli/commands/pack/LintPackCommand.js.map +1 -1
- package/dist/scripts/cli/commands/pack/pack-args.js.map +1 -1
- package/dist/scripts/cli/commands/project/AddCommand.js.map +1 -1
- package/dist/scripts/cli/commands/project/AdoptCommand.js +62 -2
- package/dist/scripts/cli/commands/project/AdoptCommand.js.map +1 -1
- package/dist/scripts/cli/commands/project/InitFromPackCommand.js.map +1 -1
- package/dist/scripts/cli/commands/project/InitProjectCommand.js +2 -1
- package/dist/scripts/cli/commands/project/InitProjectCommand.js.map +1 -1
- package/dist/scripts/cli/commands/project/OnboardCommand.js +40 -4
- package/dist/scripts/cli/commands/project/OnboardCommand.js.map +1 -1
- package/dist/scripts/cli/commands/project/UpdateCommand.js +4 -0
- package/dist/scripts/cli/commands/project/UpdateCommand.js.map +1 -1
- package/dist/scripts/cli/commands/project/WizardCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/CheckCommand.js +86 -10
- package/dist/scripts/cli/commands/quality/CheckCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/DoctorCommand.js +8 -4
- package/dist/scripts/cli/commands/quality/DoctorCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/FixCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/LicenseCheckCommand.js +3 -1
- package/dist/scripts/cli/commands/quality/LicenseCheckCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/ReportCommand.js +8 -3
- package/dist/scripts/cli/commands/quality/ReportCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js +39 -30
- package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/VerifyCommand.js +3 -2
- package/dist/scripts/cli/commands/quality/VerifyCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/DoneCommand.js +182 -77
- package/dist/scripts/cli/commands/spec/DoneCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/DraftCommand.js +292 -56
- package/dist/scripts/cli/commands/spec/DraftCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/ExploreCommand.js +224 -0
- package/dist/scripts/cli/commands/spec/ExploreCommand.js.map +1 -0
- package/dist/scripts/cli/commands/spec/MatrixCommand.js +35 -5
- package/dist/scripts/cli/commands/spec/MatrixCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/NewCommand.js +4 -4
- package/dist/scripts/cli/commands/spec/NewCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/PetCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/PlanCommand.js +8 -3
- package/dist/scripts/cli/commands/spec/PlanCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/ReqCommand.js +8 -9
- package/dist/scripts/cli/commands/spec/ReqCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/StatusCommand.js +105 -26
- package/dist/scripts/cli/commands/spec/StatusCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/StudioCommand.js +76 -4
- package/dist/scripts/cli/commands/spec/StudioCommand.js.map +1 -1
- package/dist/scripts/cli/commands/specops/ContributeCommand.js +6 -2
- package/dist/scripts/cli/commands/specops/ContributeCommand.js.map +1 -1
- package/dist/scripts/cli/commands/specops/DiffCommand.js +19 -5
- package/dist/scripts/cli/commands/specops/DiffCommand.js.map +1 -1
- package/dist/scripts/cli/commands/specops/SyncCommand.js +4 -2
- package/dist/scripts/cli/commands/specops/SyncCommand.js.map +1 -1
- package/dist/scripts/docs/build-site.js.map +1 -1
- package/dist/scripts/docs/icons.js +0 -6
- package/dist/scripts/docs/icons.js.map +1 -1
- package/dist/scripts/docs/journey-storyboard.js +56 -47
- package/dist/scripts/docs/journey-storyboard.js.map +1 -1
- package/dist/scripts/docs/nav.js +5 -0
- package/dist/scripts/docs/nav.js.map +1 -1
- package/dist/scripts/docs/record-journey.js +93 -30
- package/dist/scripts/docs/record-journey.js.map +1 -1
- package/dist/scripts/docs/record-terminal.js +1 -1
- package/dist/scripts/docs/record-terminal.js.map +1 -1
- package/dist/scripts/docs/terminal-storyboard.js +24 -21
- package/dist/scripts/docs/terminal-storyboard.js.map +1 -1
- package/dist/scripts/draft/brief-check.js +34 -0
- package/dist/scripts/draft/brief-check.js.map +1 -0
- package/dist/scripts/draft/brief-setup.js +190 -24
- package/dist/scripts/draft/brief-setup.js.map +1 -1
- package/dist/scripts/draft/confined-agent.js +1 -0
- package/dist/scripts/draft/confined-agent.js.map +1 -1
- package/dist/scripts/draft/drafting-guide.js +49 -6
- package/dist/scripts/draft/drafting-guide.js.map +1 -1
- package/dist/scripts/draft/ticket-guide.js +129 -0
- package/dist/scripts/draft/ticket-guide.js.map +1 -0
- package/dist/scripts/explore.js +10 -0
- package/dist/scripts/explore.js.map +1 -0
- package/dist/scripts/gen_mcp_tools.js +15 -7
- package/dist/scripts/gen_mcp_tools.js.map +1 -1
- package/dist/scripts/harness/events.js +202 -0
- package/dist/scripts/harness/events.js.map +1 -0
- package/dist/scripts/harness/init.js +6 -2
- package/dist/scripts/harness/init.js.map +1 -1
- package/dist/scripts/harness/run.js +2 -1
- package/dist/scripts/harness/run.js.map +1 -1
- package/dist/scripts/lib/agent.js.map +1 -1
- package/dist/scripts/lib/base-ids.js +179 -0
- package/dist/scripts/lib/base-ids.js.map +1 -0
- package/dist/scripts/lib/capability-specs.js.map +1 -1
- package/dist/scripts/lib/diagnostics.js +0 -10
- package/dist/scripts/lib/diagnostics.js.map +1 -1
- package/dist/scripts/lib/harness-events.js +74 -0
- package/dist/scripts/lib/harness-events.js.map +1 -0
- package/dist/scripts/lib/in-flight.js +191 -0
- package/dist/scripts/lib/in-flight.js.map +1 -0
- package/dist/scripts/lib/live-model.js +145 -0
- package/dist/scripts/lib/live-model.js.map +1 -0
- package/dist/scripts/lib/live-page.js +89 -0
- package/dist/scripts/lib/live-page.js.map +1 -0
- package/dist/scripts/lib/monorepo-fanout.js.map +1 -1
- package/dist/scripts/lib/placeholders.js.map +1 -1
- package/dist/scripts/lib/project-root.js.map +1 -1
- package/dist/scripts/lib/run-with-deadline.js +6 -1
- package/dist/scripts/lib/run-with-deadline.js.map +1 -1
- package/dist/scripts/lib/surface.js +95 -30
- package/dist/scripts/lib/surface.js.map +1 -1
- package/dist/scripts/lib/ticket-keys.js +140 -0
- package/dist/scripts/lib/ticket-keys.js.map +1 -0
- package/dist/scripts/lib/tour.js +149 -0
- package/dist/scripts/lib/tour.js.map +1 -0
- package/dist/scripts/propose.js +27 -0
- package/dist/scripts/propose.js.map +1 -0
- package/dist/scripts/schema/cli.js +1 -1
- package/dist/scripts/schema/cli.js.map +1 -1
- package/dist/scripts/schema/registry.js +1 -0
- package/dist/scripts/schema/registry.js.map +1 -1
- package/dist/scripts/specops/add.js +2 -1
- package/dist/scripts/specops/add.js.map +1 -1
- package/dist/scripts/specops/against_lock.js +22 -25
- package/dist/scripts/specops/against_lock.js.map +1 -1
- package/dist/scripts/specops/as_change.js.map +1 -1
- package/dist/scripts/specops/lock.js +0 -5
- package/dist/scripts/specops/lock.js.map +1 -1
- package/dist/scripts/specops/manifest.js +0 -8
- package/dist/scripts/specops/manifest.js.map +1 -1
- package/dist/scripts/specops/remove.js +2 -1
- package/dist/scripts/specops/remove.js.map +1 -1
- package/dist/scripts/specops/verify.js.map +1 -1
- package/dist/scripts/vcs/github-pr.js +88 -0
- package/dist/scripts/vcs/github-pr.js.map +1 -0
- package/package.json +1 -1
- package/templates/adopt/spec.md.tpl +1 -1
- package/templates/adopt/traceability.md.tpl +1 -1
- package/templates/base/AGENTS.md.tpl +14 -7
- package/templates/harness/prompt-prefix.md.tpl +1 -1
|
@@ -57,6 +57,9 @@ exports.draftReport = draftReport;
|
|
|
57
57
|
exports.moduleStatus = moduleStatus;
|
|
58
58
|
exports.briefProgress = briefProgress;
|
|
59
59
|
exports.draftNextCommand = draftNextCommand;
|
|
60
|
+
exports.openFindings = openFindings;
|
|
61
|
+
exports.nextScenario = nextScenario;
|
|
62
|
+
exports.reserveScenarioRange = reserveScenarioRange;
|
|
60
63
|
exports.pasteLine = pasteLine;
|
|
61
64
|
const fs = __importStar(require("node:fs"));
|
|
62
65
|
const path = __importStar(require("node:path"));
|
|
@@ -69,10 +72,13 @@ const project_root_1 = require("../../../lib/project-root");
|
|
|
69
72
|
const DraftChecklist_1 = require("../../../../packages/core/src/domain/DraftChecklist");
|
|
70
73
|
const ChangeWorkspace_1 = require("../../../../packages/core/src/infrastructure/ChangeWorkspace");
|
|
71
74
|
const ChangeTemplates_1 = require("../../../../packages/core/src/domain/ChangeTemplates");
|
|
75
|
+
const IdsAcrossBranches_1 = require("../../../../packages/core/src/infrastructure/IdsAcrossBranches");
|
|
72
76
|
const DiskLanguageRepository_1 = require("../../../../packages/core/src/infrastructure/DiskLanguageRepository");
|
|
73
77
|
const drafting_guide_1 = require("../../../draft/drafting-guide");
|
|
74
78
|
const confined_agent_1 = require("../../../draft/confined-agent");
|
|
75
79
|
const brief_setup_1 = require("../../../draft/brief-setup");
|
|
80
|
+
const brief_check_1 = require("../../../draft/brief-check");
|
|
81
|
+
const BriefCards_1 = require("../../../../packages/core/src/domain/BriefCards");
|
|
76
82
|
const GherkinTags_1 = require("../../../../packages/core/src/domain/GherkinTags");
|
|
77
83
|
const DerivedMatrixSources_1 = require("../../../../packages/core/src/infrastructure/DerivedMatrixSources");
|
|
78
84
|
const CHANGES = path.join("docs", "specs", "changes");
|
|
@@ -84,11 +90,13 @@ function draftModuleOf(changeId) {
|
|
|
84
90
|
return changeId.startsWith("draft-") ? changeId.slice("draft-".length) : `--check ${changeId}`;
|
|
85
91
|
}
|
|
86
92
|
function usage() {
|
|
87
|
-
return ("
|
|
93
|
+
return ("Same as specgate propose --from-brief: a module of your brief becomes a draft change.\n\n" +
|
|
94
|
+
"Usage:\n" +
|
|
88
95
|
" specgate draft your brief's modules, and the one to do next\n" +
|
|
89
96
|
" specgate draft <module> open that module's draft — or check it, once it exists\n" +
|
|
90
97
|
' specgate draft <module> --agent "claude -p"\n' +
|
|
91
98
|
" have your agent write it, then check it\n" +
|
|
99
|
+
" specgate draft --check the brief rules (B1–B11) on the brief, before drafting\n" +
|
|
92
100
|
" specgate draft --check <change-id> the checklist on any change\n" +
|
|
93
101
|
" specgate draft --new-brief write docs/brief.md to fill in, when you have none\n" +
|
|
94
102
|
' specgate draft --agent "claude -p" have your agent propose the brief\'s front matter\n\n' +
|
|
@@ -107,11 +115,20 @@ function usage() {
|
|
|
107
115
|
" D9 every scenario passes the harness gate's rules\n" +
|
|
108
116
|
" D10 no id the project already uses\n" +
|
|
109
117
|
" D12 an actor can get the ids it acts on\n\n" +
|
|
110
|
-
"Without a brief, D3 and D6 cannot run, and the report says so.\n"
|
|
118
|
+
"Without a brief, D3 and D6 cannot run, and the report says so.\n\n" +
|
|
119
|
+
"The brief rules (ADR-0038) — errors stop a draft, warnings are for you:\n" +
|
|
120
|
+
" B1 front matter fields in the shape Specgate reads B5 a vague word in a rule\n" +
|
|
121
|
+
" B2 the brief, and each module section, says something B6 a promised quantity with no number\n" +
|
|
122
|
+
" B3 a section per module, a module per section B7 a framework or database in a rule\n" +
|
|
123
|
+
" B4 every module has rules, and examples B8 more than 7 rules or 3 questions\n" +
|
|
124
|
+
" B9 a declared actor never mentioned\n" +
|
|
125
|
+
" B10 a rule that names a non-goal\n" +
|
|
126
|
+
" B11 a condition after the rule (EARS)\n");
|
|
111
127
|
}
|
|
112
128
|
function parseArgs(argv) {
|
|
113
129
|
const o = {
|
|
114
130
|
check: "",
|
|
131
|
+
checkBrief: false,
|
|
115
132
|
module: "",
|
|
116
133
|
brief: "",
|
|
117
134
|
maxRequirements: 25,
|
|
@@ -129,8 +146,14 @@ function parseArgs(argv) {
|
|
|
129
146
|
throw new Error(`${a} needs a value`);
|
|
130
147
|
return v;
|
|
131
148
|
};
|
|
132
|
-
if (a === "--check")
|
|
133
|
-
|
|
149
|
+
if (a === "--check") {
|
|
150
|
+
// `--check <change-id>` checks a draft; `--check` alone, the brief.
|
|
151
|
+
const next = argv[i + 1];
|
|
152
|
+
if (next === undefined || next.startsWith("-"))
|
|
153
|
+
o.checkBrief = true;
|
|
154
|
+
else
|
|
155
|
+
o.check = value();
|
|
156
|
+
}
|
|
134
157
|
else if (a === "--brief")
|
|
135
158
|
o.brief = value();
|
|
136
159
|
else if (a === "--max-requirements")
|
|
@@ -157,6 +180,9 @@ function parseArgs(argv) {
|
|
|
157
180
|
if (o.check && o.module) {
|
|
158
181
|
throw new Error("give a module or --check <change-id>, not both");
|
|
159
182
|
}
|
|
183
|
+
if (o.checkBrief && (o.module || o.agent || o.newBrief)) {
|
|
184
|
+
throw new Error("--check only checks the brief: run it on its own");
|
|
185
|
+
}
|
|
160
186
|
if (o.module && !ChangeWorkspace_1.CHANGE_ID_RE.test(o.module)) {
|
|
161
187
|
throw new Error(`"${o.module}" is not a module name: use lowercase words and dashes, e.g. bookings`);
|
|
162
188
|
}
|
|
@@ -340,11 +366,17 @@ function briefProgress(projectDir) {
|
|
|
340
366
|
const modules = (0, DraftChecklist_1.briefModules)(text).map((m) => moduleStatus(projectDir, m));
|
|
341
367
|
if (modules.length === 0)
|
|
342
368
|
return null;
|
|
343
|
-
|
|
369
|
+
// A ready draft is reviewed and then applied (ADR-0035): the next module to
|
|
370
|
+
// draft is the first that is neither archived nor ready.
|
|
371
|
+
return {
|
|
372
|
+
brief: exports.DEFAULT_BRIEF,
|
|
373
|
+
modules,
|
|
374
|
+
next: modules.find((m) => m.state !== "archived" && m.state !== "ready"),
|
|
375
|
+
};
|
|
344
376
|
}
|
|
345
377
|
/** What to run for this module, given where it stands. */
|
|
346
378
|
function draftNextCommand(s) {
|
|
347
|
-
return s.state === "ready" ?
|
|
379
|
+
return s.state === "ready" ? "specgate apply" : `specgate propose --from-brief ${s.module}`;
|
|
348
380
|
}
|
|
349
381
|
/** `➜ claude -p … is drafting catalogo…`, and why a flag was added. */
|
|
350
382
|
function agentLine(command, doing, added) {
|
|
@@ -421,7 +453,8 @@ function nextScenario(projectDir) {
|
|
|
421
453
|
const full = path.join(dir, e.name);
|
|
422
454
|
if (e.isDirectory())
|
|
423
455
|
walk(full);
|
|
424
|
-
|
|
456
|
+
// change.yaml holds a change's reserved scenario range (REQ-157).
|
|
457
|
+
else if (e.name.endsWith(".md") || e.name.endsWith(".feature") || e.name === "change.yaml") {
|
|
425
458
|
for (const m of fs.readFileSync(full, "utf8").matchAll(/\bSCN-(\d+)\b/g)) {
|
|
426
459
|
max = Math.max(max, Number(m[1]));
|
|
427
460
|
}
|
|
@@ -430,11 +463,19 @@ function nextScenario(projectDir) {
|
|
|
430
463
|
};
|
|
431
464
|
walk(path.join(projectDir, "docs", "specs"));
|
|
432
465
|
walk(path.join(projectDir, "features"));
|
|
466
|
+
max = Math.max(max, (0, IdsAcrossBranches_1.highestOnOtherBranches)(projectDir, "SCN"));
|
|
433
467
|
for (const m of (readIf(path.join(projectDir, "spec.md")) || "").matchAll(/\bSCN-(\d+)\b/g)) {
|
|
434
468
|
max = Math.max(max, Number(m[1]));
|
|
435
469
|
}
|
|
436
470
|
return `SCN-${String(max + 1).padStart(3, "0")}`;
|
|
437
471
|
}
|
|
472
|
+
/** Three scenario ids per requirement id, from the next free one (REQ-157). */
|
|
473
|
+
function reserveScenarioRange(projectDir, reqRange) {
|
|
474
|
+
const first = nextScenario(projectDir);
|
|
475
|
+
const reqs = Number(reqRange[1].slice(4)) - Number(reqRange[0].slice(4)) + 1;
|
|
476
|
+
const last = Number(first.slice(4)) + Math.max(1, reqs) * 3 - 1;
|
|
477
|
+
return [first, `SCN-${String(last).padStart(3, "0")}`];
|
|
478
|
+
}
|
|
438
479
|
/** The one line to paste into any agent. */
|
|
439
480
|
function pasteLine(module) {
|
|
440
481
|
return `Draft the ${module} module: follow ${CHANGES.split(path.sep).join("/")}/draft-${module}/DRAFTING.md`;
|
|
@@ -456,11 +497,23 @@ function proposalFor(module, changeId, brief) {
|
|
|
456
497
|
"",
|
|
457
498
|
].join("\n");
|
|
458
499
|
}
|
|
500
|
+
/** The module's section of the brief, for `DRAFTING.md`; undefined when it has none. */
|
|
501
|
+
function moduleCards(briefText, module) {
|
|
502
|
+
const section = (0, BriefCards_1.moduleSection)(briefText, module);
|
|
503
|
+
if (!section)
|
|
504
|
+
return undefined;
|
|
505
|
+
return {
|
|
506
|
+
source: section.source,
|
|
507
|
+
values: (0, BriefCards_1.cardValues)(section),
|
|
508
|
+
questions: section.questions.map((q) => q.text),
|
|
509
|
+
};
|
|
510
|
+
}
|
|
459
511
|
/** Open `draft-<module>`: the change, linked to the brief, with its guide. */
|
|
460
512
|
function openDraft(projectDir, module, brief, o) {
|
|
461
513
|
const changeId = `draft-${module}`;
|
|
462
514
|
const dir = path.join(projectDir, CHANGES, changeId);
|
|
463
515
|
const reqRange = (0, ChangeWorkspace_1.reserveReqRange)(projectDir, o.reserve);
|
|
516
|
+
const scnRange = reserveScenarioRange(projectDir, reqRange);
|
|
464
517
|
const config = {
|
|
465
518
|
...ChangeWorkspace_1.DEFAULT_CONFIG,
|
|
466
519
|
created: new Date().toISOString().slice(0, 10),
|
|
@@ -480,12 +533,14 @@ function openDraft(projectDir, module, brief, o) {
|
|
|
480
533
|
changeId,
|
|
481
534
|
brief,
|
|
482
535
|
reqRange,
|
|
483
|
-
firstScenario:
|
|
536
|
+
firstScenario: scnRange[0],
|
|
537
|
+
lastScenario: scnRange[1],
|
|
484
538
|
maxRequirements: o.maxRequirements,
|
|
485
539
|
actors: (0, DraftChecklist_1.actorSurfaces)(briefText),
|
|
540
|
+
cards: moduleCards(briefText, module),
|
|
486
541
|
}));
|
|
487
542
|
write(path.join("specs", module, "spec.md"), (0, ChangeTemplates_1.templateDelta)(module, reqRange[0], (0, DiskLanguageRepository_1.phrases)(projectDir)));
|
|
488
|
-
write("change.yaml", `${(0, ChangeWorkspace_1.renderConfig)(config).trimEnd()}\nbrief: ${brief}\n`);
|
|
543
|
+
write("change.yaml", `${(0, ChangeWorkspace_1.renderConfig)(config).trimEnd()}\nscn_range: [${scnRange[0]}, ${scnRange[1]}]\nbrief: ${brief}\n`);
|
|
489
544
|
write("proposal.md", proposalFor(module, changeId, brief));
|
|
490
545
|
return { changeId, reqRange, created };
|
|
491
546
|
}
|
|
@@ -512,6 +567,12 @@ function nextFor(s) {
|
|
|
512
567
|
return s ? draftNextCommand(s) : "specgate status";
|
|
513
568
|
}
|
|
514
569
|
class DraftCommand extends command_1.BaseCommand {
|
|
570
|
+
/** How the person called it, for the header: `draft`, or `propose --from-brief`. */
|
|
571
|
+
verb;
|
|
572
|
+
constructor(args = [], verb = "draft") {
|
|
573
|
+
super(args);
|
|
574
|
+
this.verb = verb;
|
|
575
|
+
}
|
|
515
576
|
execute() {
|
|
516
577
|
const io0 = (0, agent_1.agentIo)((0, agent_1.wantsJson)(this.args));
|
|
517
578
|
let o;
|
|
@@ -519,11 +580,14 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
519
580
|
o = parseArgs(this.args);
|
|
520
581
|
}
|
|
521
582
|
catch (err) {
|
|
583
|
+
// `parseArgs` throws Errors.
|
|
522
584
|
io0.usage({ draft: null }, [
|
|
523
585
|
(0, diagnostics_1.error)("draft_usage", err.message, { fix: "specgate draft --help" }),
|
|
524
586
|
]);
|
|
525
587
|
}
|
|
526
588
|
const opts = o;
|
|
589
|
+
if (opts.checkBrief)
|
|
590
|
+
return this.checkBrief(opts);
|
|
527
591
|
if (opts.check)
|
|
528
592
|
return this.check(opts);
|
|
529
593
|
if (opts.module)
|
|
@@ -539,7 +603,7 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
539
603
|
const io = (0, agent_1.agentIo)(opts.json);
|
|
540
604
|
io.fail({ draft: null }, [
|
|
541
605
|
(0, diagnostics_1.error)("draft_no_brief", `No plan or brief at ${rel}.`, {
|
|
542
|
-
fix: `Save your plan or brief as ${exports.DEFAULT_BRIEF} — any markdown — then run \`specgate
|
|
606
|
+
fix: `Save your plan or brief as ${exports.DEFAULT_BRIEF} — any markdown — then run \`specgate propose --from-brief\`. ` +
|
|
543
607
|
"Somewhere else: --brief <file>.",
|
|
544
608
|
}),
|
|
545
609
|
]);
|
|
@@ -556,14 +620,14 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
556
620
|
// (a) No brief: what one is, and how to get one.
|
|
557
621
|
if (text === undefined) {
|
|
558
622
|
if (!opts.json) {
|
|
559
|
-
process.stdout.write(ui.header(
|
|
623
|
+
process.stdout.write(ui.header(this.verb) + `${(0, brief_setup_1.noBriefExplanation)(rel)}\n`);
|
|
560
624
|
process.exitCode = 1;
|
|
561
625
|
return;
|
|
562
626
|
}
|
|
563
627
|
io.fail({ draft: null }, [
|
|
564
628
|
(0, diagnostics_1.error)("draft_no_brief", `No plan or brief at ${rel}.`, {
|
|
565
|
-
fix: `Save your plan or brief as ${rel} — any markdown — then run \`specgate
|
|
566
|
-
"No plan yet: `specgate
|
|
629
|
+
fix: `Save your plan or brief as ${rel} — any markdown — then run \`specgate propose --from-brief\`. ` +
|
|
630
|
+
"No plan yet: `specgate propose --from-brief --new-brief` writes one to fill in. Somewhere else: --brief <file>.",
|
|
567
631
|
}),
|
|
568
632
|
]);
|
|
569
633
|
}
|
|
@@ -579,49 +643,75 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
579
643
|
frontMatter: null,
|
|
580
644
|
guide: brief_setup_1.BRIEF_SETUP_FILE,
|
|
581
645
|
prompt: brief_setup_1.BRIEF_SETUP_PASTE,
|
|
582
|
-
next: 'specgate
|
|
646
|
+
next: 'specgate propose --from-brief --agent "claude -p"',
|
|
583
647
|
},
|
|
584
|
-
}, () => process.stdout.write(ui.header(
|
|
648
|
+
}, () => process.stdout.write(ui.header(this.verb, rel) +
|
|
585
649
|
(0, brief_setup_1.frontMatterExplanation)(rel) +
|
|
586
650
|
"\n" +
|
|
587
651
|
ui.section("Or let your agent propose it") +
|
|
588
652
|
" Paste this into Claude Code, Cursor, Codex…\n\n" +
|
|
589
653
|
ui.quote(brief_setup_1.BRIEF_SETUP_PASTE) +
|
|
590
|
-
ui.next('specgate
|
|
654
|
+
ui.next('specgate propose --from-brief --agent "claude -p"', "Specgate calls your agent for you — then review the four fields and run specgate propose --from-brief again")));
|
|
591
655
|
return;
|
|
592
656
|
}
|
|
593
657
|
// (c) A front matter with problems: each in plain words, with its fix.
|
|
594
658
|
const problems = (0, brief_setup_1.checkFrontMatter)(brief.text);
|
|
595
659
|
if (problems.length > 0) {
|
|
596
660
|
io.emit({
|
|
597
|
-
draft: {
|
|
661
|
+
draft: {
|
|
662
|
+
brief: rel,
|
|
663
|
+
frontMatter: "invalid",
|
|
664
|
+
next: `edit ${rel}, then specgate propose --from-brief`,
|
|
665
|
+
},
|
|
598
666
|
status: problems.map((p) => (0, diagnostics_1.error)(p.code, p.message, { fix: p.fix })),
|
|
599
667
|
}, () => {
|
|
600
668
|
const out = process.stdout;
|
|
601
|
-
out.write(ui.header(
|
|
669
|
+
out.write(ui.header(this.verb, rel));
|
|
602
670
|
out.write(` The front matter at the top of ${rel} needs ${problems.length} fix(es):\n\n`);
|
|
603
671
|
for (const p of problems)
|
|
604
672
|
out.write(ui.fail(p.message) + ui.fix(p.fix));
|
|
605
|
-
out.write(ui.next("specgate
|
|
673
|
+
out.write(ui.next("specgate propose --from-brief", `once they are fixed in ${rel}`));
|
|
606
674
|
});
|
|
607
675
|
process.exitCode = 1;
|
|
608
676
|
return;
|
|
609
677
|
}
|
|
678
|
+
// (c') The body: an empty brief, a module without its section or cards.
|
|
679
|
+
const findings = (0, brief_check_1.checkBrief)(brief.text, rel);
|
|
680
|
+
const errors = findings.filter((d) => d.severity === "error");
|
|
681
|
+
if (errors.length > 0) {
|
|
682
|
+
io.emit({
|
|
683
|
+
draft: { brief: rel, ready: false, next: "specgate propose --from-brief --check" },
|
|
684
|
+
status: findings,
|
|
685
|
+
}, () => {
|
|
686
|
+
const out = process.stdout;
|
|
687
|
+
out.write(ui.header(this.verb, rel));
|
|
688
|
+
out.write(` ${rel} needs ${errors.length} fix(es) before a module is drafted:\n\n`);
|
|
689
|
+
writeBriefFindings(errors);
|
|
690
|
+
out.write(ui.next("specgate propose --from-brief --check", `once they are fixed in ${rel}`));
|
|
691
|
+
});
|
|
692
|
+
process.exitCode = 1;
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
const warnings = findings.filter((d) => d.severity === "warning").length;
|
|
610
696
|
// (d) Ready: the modules, where each stands, what is next.
|
|
611
697
|
const modules = (0, DraftChecklist_1.briefModules)(brief.text);
|
|
612
698
|
const states = modules.map((m) => moduleStatus(projectDir, m, rel));
|
|
613
699
|
const pending = states.find((s) => s.state !== "archived");
|
|
614
700
|
const next = nextFor(pending);
|
|
615
701
|
const product = /^product:\s*(.+)$/m.exec((0, brief_setup_1.frontMatter)(brief.text) || "");
|
|
616
|
-
io.emit({ draft: { brief: rel, modules: states, next } }, () => {
|
|
702
|
+
io.emit({ draft: { brief: rel, modules: states, next }, status: findings }, () => {
|
|
617
703
|
const out = process.stdout;
|
|
618
|
-
out.write(ui.header(
|
|
704
|
+
out.write(ui.header(this.verb, rel));
|
|
619
705
|
if (product)
|
|
620
706
|
out.write(ui.kv("Product", ui.bold(product[1].trim()), 10));
|
|
621
707
|
const archived = states.filter((s) => s.state === "archived").length;
|
|
622
708
|
out.write(ui.kv("Progress", ui.progress(archived, states.length, "archived"), 10));
|
|
623
709
|
out.write("\n" + ui.section("Modules"));
|
|
624
710
|
out.write(ui.table(states.map((s) => [`${ICON[s.state]()} ${s.module}`, ui.muted(describe(s))])));
|
|
711
|
+
if (warnings > 0) {
|
|
712
|
+
out.write("\n" +
|
|
713
|
+
ui.info(`${warnings} warning(s) on the brief's wording ${ui.muted("— specgate propose --from-brief --check")}`));
|
|
714
|
+
}
|
|
625
715
|
out.write(pending
|
|
626
716
|
? ui.next(next)
|
|
627
717
|
: "\n" +
|
|
@@ -629,29 +719,60 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
629
719
|
ui.next(next, "what to build first"));
|
|
630
720
|
});
|
|
631
721
|
}
|
|
722
|
+
/** `--check`: the brief rules on the brief, writing nothing (ADR-0038). */
|
|
723
|
+
checkBrief(opts) {
|
|
724
|
+
const io = (0, agent_1.agentIo)(opts.json);
|
|
725
|
+
const projectDir = (0, project_root_1.resolveProjectDir)(opts.projectDir);
|
|
726
|
+
const brief = this.brief(projectDir, opts);
|
|
727
|
+
const findings = (0, brief_check_1.checkBrief)(brief.text, brief.rel);
|
|
728
|
+
const errors = findings.filter((d) => d.severity === "error").length;
|
|
729
|
+
const warnings = findings.filter((d) => d.severity === "warning").length;
|
|
730
|
+
const first = (0, DraftChecklist_1.briefModules)(brief.text).find((m) => moduleStatus(projectDir, m, brief.rel).state !== "archived");
|
|
731
|
+
const next = errors > 0
|
|
732
|
+
? "specgate propose --from-brief --check"
|
|
733
|
+
: first
|
|
734
|
+
? `specgate propose --from-brief ${first}`
|
|
735
|
+
: "specgate propose --from-brief";
|
|
736
|
+
io.emit({
|
|
737
|
+
draft: { brief: brief.rel, ready: errors === 0, errors, warnings, next },
|
|
738
|
+
status: findings,
|
|
739
|
+
}, () => {
|
|
740
|
+
const out = process.stdout;
|
|
741
|
+
out.write(ui.header(`${this.verb} --check`, brief.rel));
|
|
742
|
+
writeBriefFindings(findings);
|
|
743
|
+
out.write(errors > 0
|
|
744
|
+
? (findings.length ? "\n" : "") +
|
|
745
|
+
ui.fail(`${ui.bold(`${errors} error(s) to fix before drafting`)} ${ui.muted("— each says how")}`)
|
|
746
|
+
: (findings.length ? "\n" : "") +
|
|
747
|
+
ui.ok(ui.bold("Ready to draft") +
|
|
748
|
+
(warnings ? ui.muted(` — ${warnings} warning(s) for you to judge`) : "")));
|
|
749
|
+
out.write(ui.next(next, errors > 0 ? `once they are fixed in ${brief.rel}` : "the first module"));
|
|
750
|
+
});
|
|
751
|
+
process.exitCode = errors > 0 ? 1 : 0;
|
|
752
|
+
}
|
|
632
753
|
/** `--new-brief`: the template, never over a brief that exists. */
|
|
633
754
|
newBrief(opts, file, rel) {
|
|
634
755
|
const io = (0, agent_1.agentIo)(opts.json);
|
|
635
756
|
if (fs.existsSync(file)) {
|
|
636
757
|
io.fail({ draft: null }, [
|
|
637
758
|
(0, diagnostics_1.error)("draft_brief_exists", `${rel} already exists — nothing was written.`, {
|
|
638
|
-
fix: "Edit it, then run `specgate
|
|
759
|
+
fix: "Edit it, then run `specgate propose --from-brief`; it says what the top of the brief still needs.",
|
|
639
760
|
}),
|
|
640
761
|
]);
|
|
641
762
|
}
|
|
642
763
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
643
764
|
fs.writeFileSync(file, brief_setup_1.BRIEF_TEMPLATE, "utf8");
|
|
644
|
-
io.emit({ draft: { brief: rel, written: true, next: "specgate
|
|
765
|
+
io.emit({ draft: { brief: rel, written: true, next: "specgate propose --from-brief --check" } }, () => process.stdout.write(ui.header(`${this.verb} --new-brief`) +
|
|
645
766
|
ui.ok(`Wrote ${rel} — a brief to fill in.`) +
|
|
646
767
|
"\n Fill it in, in your own words:\n" +
|
|
647
768
|
" 1. The four fields at the top — product, surfaces, actors, modules.\n" +
|
|
648
769
|
" Each has an example in the comment above it.\n" +
|
|
649
|
-
" 2.
|
|
650
|
-
"
|
|
651
|
-
"
|
|
652
|
-
" Have a plan or an email already? Paste it under
|
|
653
|
-
|
|
654
|
-
ui.next("specgate
|
|
770
|
+
" 2. Why — the problem, and a success you can measure.\n" +
|
|
771
|
+
" 3. Per module, a section: its Rules, an Example per rule, and the\n" +
|
|
772
|
+
" Questions you cannot answer yet.\n\n" +
|
|
773
|
+
" Have a plan or an email already? Paste it under Notes — your agent can\n" +
|
|
774
|
+
' turn it into the module sections: specgate propose --from-brief --agent "claude -p"\n' +
|
|
775
|
+
ui.next("specgate propose --from-brief --check", "it checks the brief and says what is missing")));
|
|
655
776
|
}
|
|
656
777
|
/**
|
|
657
778
|
* `draft --agent` without a module: the agent proposes the front matter.
|
|
@@ -662,10 +783,20 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
662
783
|
frontMatterWithAgent(opts, projectDir, brief) {
|
|
663
784
|
const io = (0, agent_1.agentIo)(opts.json);
|
|
664
785
|
if ((0, brief_setup_1.frontMatter)(brief.text) !== null && (0, brief_setup_1.checkFrontMatter)(brief.text).length === 0) {
|
|
665
|
-
const
|
|
666
|
-
|
|
786
|
+
const modules = (0, DraftChecklist_1.briefModules)(brief.text);
|
|
787
|
+
if (modules.length > 0 && (0, BriefCards_1.readBriefBody)(brief.text).modules.length === 0) {
|
|
788
|
+
return this.cardsWithAgent(opts, projectDir, brief, modules);
|
|
789
|
+
}
|
|
790
|
+
const first = modules[0];
|
|
791
|
+
io.emit({
|
|
792
|
+
draft: {
|
|
793
|
+
brief: brief.rel,
|
|
794
|
+
frontMatter: "ok",
|
|
795
|
+
next: `specgate propose --from-brief ${first}`,
|
|
796
|
+
},
|
|
797
|
+
}, () => process.stdout.write(ui.header(`${this.verb} --agent`, brief.rel) +
|
|
667
798
|
ui.ok(`The front matter of ${brief.rel} is already there and complete — nothing for the agent to do.`) +
|
|
668
|
-
ui.next(`specgate
|
|
799
|
+
ui.next(`specgate propose --from-brief ${first}`)));
|
|
669
800
|
return;
|
|
670
801
|
}
|
|
671
802
|
writeSetupGuide(projectDir, brief.rel);
|
|
@@ -675,7 +806,7 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
675
806
|
.join("/");
|
|
676
807
|
const agent = (0, confined_agent_1.withWritePermission)(opts.agent);
|
|
677
808
|
if (!opts.json) {
|
|
678
|
-
process.stdout.write(ui.header(
|
|
809
|
+
process.stdout.write(ui.header(`${this.verb} --agent`, brief.rel) +
|
|
679
810
|
agentLine(agent.command, `is proposing the front matter of ${brief.rel}`, agent.added));
|
|
680
811
|
}
|
|
681
812
|
const run = (0, confined_agent_1.runConfinedAgent)({
|
|
@@ -703,7 +834,7 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
703
834
|
fs.writeFileSync(path.resolve(projectDir, brief.rel), brief.text, "utf8");
|
|
704
835
|
io.fail({ draft: null }, [
|
|
705
836
|
(0, diagnostics_1.error)("draft_brief_body_changed", "The agent changed the brief itself, not only its front matter — undone.", {
|
|
706
|
-
fix: `${brief.rel} is back as it was. Try again, or write the four fields yourself (\`specgate
|
|
837
|
+
fix: `${brief.rel} is back as it was. Try again, or write the four fields yourself (\`specgate propose --from-brief\` shows them).`,
|
|
707
838
|
}),
|
|
708
839
|
]);
|
|
709
840
|
}
|
|
@@ -718,13 +849,13 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
718
849
|
if (proposed === null) {
|
|
719
850
|
io.fail({ draft: null }, [
|
|
720
851
|
(0, diagnostics_1.error)("draft_no_front_matter", "The agent wrote no front matter.", {
|
|
721
|
-
fix: `${confined_agent_1.NO_WRITE_HINT} Or run \`specgate
|
|
852
|
+
fix: `${confined_agent_1.NO_WRITE_HINT} Or run \`specgate propose --from-brief\` to see the four fields, and write them yourself.`,
|
|
722
853
|
}),
|
|
723
854
|
]);
|
|
724
855
|
}
|
|
725
856
|
const problems = (0, brief_setup_1.checkFrontMatter)(after);
|
|
726
857
|
io.emit({
|
|
727
|
-
draft: { brief: brief.rel, frontMatter: proposed, next: "specgate
|
|
858
|
+
draft: { brief: brief.rel, frontMatter: proposed, next: "specgate propose --from-brief" },
|
|
728
859
|
status: problems.map((p) => (0, diagnostics_1.error)(p.code, p.message, { fix: p.fix })),
|
|
729
860
|
}, () => {
|
|
730
861
|
const out = process.stdout;
|
|
@@ -737,7 +868,81 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
737
868
|
for (const p of problems)
|
|
738
869
|
out.write(ui.fail(p.message) + ui.fix(p.fix));
|
|
739
870
|
}
|
|
740
|
-
out.write(ui.next("specgate
|
|
871
|
+
out.write(ui.next("specgate propose --from-brief", "review it — change anything — then run it"));
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* `draft --agent` on a brief with its front matter and no module sections:
|
|
876
|
+
* the agent proposes one section per module from the brief's own text. It
|
|
877
|
+
* may write only the brief, and must keep every line already there.
|
|
878
|
+
*/
|
|
879
|
+
cardsWithAgent(opts, projectDir, brief, modules) {
|
|
880
|
+
const io = (0, agent_1.agentIo)(opts.json);
|
|
881
|
+
const guide = (0, brief_setup_1.briefCardsGuide)(brief.rel, modules);
|
|
882
|
+
const guideFile = path.join(projectDir, brief_setup_1.BRIEF_CARDS_FILE);
|
|
883
|
+
fs.mkdirSync(path.dirname(guideFile), { recursive: true });
|
|
884
|
+
fs.writeFileSync(guideFile, guide, "utf8");
|
|
885
|
+
const own = path
|
|
886
|
+
.relative(projectDir, path.resolve(projectDir, brief.rel))
|
|
887
|
+
.split(path.sep)
|
|
888
|
+
.join("/");
|
|
889
|
+
const agent = (0, confined_agent_1.withWritePermission)(opts.agent);
|
|
890
|
+
if (!opts.json) {
|
|
891
|
+
process.stdout.write(ui.header(`${this.verb} --agent`, brief.rel) +
|
|
892
|
+
agentLine(agent.command, `is proposing the module sections of ${brief.rel}`, agent.added));
|
|
893
|
+
}
|
|
894
|
+
const run = (0, confined_agent_1.runConfinedAgent)({
|
|
895
|
+
projectDir,
|
|
896
|
+
changeId: "brief",
|
|
897
|
+
allowed: (rel) => rel === own,
|
|
898
|
+
command: agent.command,
|
|
899
|
+
prompt: (0, brief_setup_1.briefCardsPrompt)(guide),
|
|
900
|
+
showOutput: !opts.json,
|
|
901
|
+
});
|
|
902
|
+
if (run.problem === "no_git") {
|
|
903
|
+
io.fail({ draft: null }, [
|
|
904
|
+
(0, diagnostics_1.error)("draft_agent_needs_git", "--agent needs a git repository.", {
|
|
905
|
+
fix: `Run \`git init\`, then try again — or paste this into your agent yourself: ${brief_setup_1.BRIEF_CARDS_PASTE}`,
|
|
906
|
+
}),
|
|
907
|
+
]);
|
|
908
|
+
}
|
|
909
|
+
if (run.reverted.length > 0 && !opts.json) {
|
|
910
|
+
process.stdout.write("\n" +
|
|
911
|
+
ui.warn(`${ui_1.sym.undo} Undone — the agent wrote outside the brief: ${run.reverted.join(", ")}`));
|
|
912
|
+
}
|
|
913
|
+
const file = path.resolve(projectDir, brief.rel);
|
|
914
|
+
const after = readIf(file) ?? "";
|
|
915
|
+
if (!(0, brief_setup_1.keepsEveryLine)(brief.text, after)) {
|
|
916
|
+
fs.writeFileSync(file, brief.text, "utf8");
|
|
917
|
+
io.fail({ draft: null }, [
|
|
918
|
+
(0, diagnostics_1.error)("draft_brief_text_lost", "The agent changed or removed a line of the brief, not only added sections — undone.", {
|
|
919
|
+
fix: `${brief.rel} is back as it was. Try again, or write the sections yourself: \`## Module: ${modules[0]}\` with Rules, Examples and Questions.`,
|
|
920
|
+
}),
|
|
921
|
+
]);
|
|
922
|
+
}
|
|
923
|
+
if (!run.ok) {
|
|
924
|
+
io.fail({ draft: null }, [
|
|
925
|
+
(0, diagnostics_1.error)("draft_agent_failed", `The agent ${run.detail}.`, {
|
|
926
|
+
fix: `Check the command runs on its own, or paste this into your agent: ${brief_setup_1.BRIEF_CARDS_PASTE}`,
|
|
927
|
+
}),
|
|
928
|
+
]);
|
|
929
|
+
}
|
|
930
|
+
const added = (0, BriefCards_1.readBriefBody)(after).modules.map((m) => m.name);
|
|
931
|
+
if (added.length === 0) {
|
|
932
|
+
io.fail({ draft: null }, [
|
|
933
|
+
(0, diagnostics_1.error)("draft_no_cards", "The agent added no module section.", {
|
|
934
|
+
fix: `${confined_agent_1.NO_WRITE_HINT} Or write them yourself: \`## Module: ${modules[0]}\` with Rules, Examples and Questions.`,
|
|
935
|
+
}),
|
|
936
|
+
]);
|
|
937
|
+
}
|
|
938
|
+
const findings = (0, brief_check_1.checkBrief)(after, brief.rel);
|
|
939
|
+
const next = "specgate propose --from-brief --check";
|
|
940
|
+
io.emit({ draft: { brief: brief.rel, sections: added, next }, status: findings }, () => {
|
|
941
|
+
const out = process.stdout;
|
|
942
|
+
out.write(ui.rule() + "\n");
|
|
943
|
+
out.write(ui.ok(`The agent proposed ${added.length} module section(s) in ${brief.rel}: ${added.join(", ")}`) + "\n");
|
|
944
|
+
out.write(ui.info("Every line you wrote is still there."));
|
|
945
|
+
out.write(ui.next(next, "review the sections — change anything — then check them"));
|
|
741
946
|
});
|
|
742
947
|
}
|
|
743
948
|
// ── specgate draft <module> ─────────────────────────────────────────────────
|
|
@@ -752,28 +957,41 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
752
957
|
module: opts.module,
|
|
753
958
|
change: changeId,
|
|
754
959
|
state: "archived",
|
|
755
|
-
next: "specgate
|
|
960
|
+
next: "specgate propose --from-brief",
|
|
756
961
|
},
|
|
757
|
-
}, () => process.stdout.write(ui.header(
|
|
962
|
+
}, () => process.stdout.write(ui.header(`${this.verb} ${opts.module}`) +
|
|
758
963
|
ui.ok(`${opts.module} is already drafted and archived ${ui.muted(`(${status.archivedAs})`)}`) +
|
|
759
|
-
ui.next("specgate
|
|
964
|
+
ui.next("specgate propose --from-brief", "the next module of the brief, or what to build first")));
|
|
760
965
|
return;
|
|
761
966
|
}
|
|
762
967
|
if (status.state === "not started") {
|
|
763
968
|
if (!fs.existsSync(path.join(projectDir, "spec.md"))) {
|
|
764
969
|
io.fail({ draft: null }, [
|
|
765
970
|
(0, diagnostics_1.error)("draft_no_project", "This folder is not a Specgate project yet.", {
|
|
766
|
-
fix: "Run `specgate init` first, then `specgate
|
|
971
|
+
fix: "Run `specgate init` first, then `specgate propose --from-brief " +
|
|
972
|
+
opts.module +
|
|
973
|
+
"`.",
|
|
767
974
|
}),
|
|
768
975
|
]);
|
|
769
976
|
}
|
|
770
977
|
const brief = this.brief(projectDir, opts);
|
|
978
|
+
// A brief that is plain markdown, with no front matter, still drafts:
|
|
979
|
+
// only the rules that apply to what it does say stop it.
|
|
980
|
+
const blocking = (0, brief_check_1.checkBrief)(brief.text, brief.rel).filter((d) => d.code !== "brief_no_front_matter");
|
|
981
|
+
if (!(0, brief_check_1.briefReady)(blocking)) {
|
|
982
|
+
io.fail({ draft: null }, [
|
|
983
|
+
(0, diagnostics_1.error)("brief_not_ready", `${brief.rel} is not ready to draft ${opts.module} from.`, {
|
|
984
|
+
fix: "Fix what `specgate propose --from-brief --check` reports, then run this again.",
|
|
985
|
+
}),
|
|
986
|
+
...blocking.filter((d) => d.severity === "error"),
|
|
987
|
+
]);
|
|
988
|
+
}
|
|
771
989
|
const listed = (0, DraftChecklist_1.briefModules)(brief.text);
|
|
772
990
|
const opened = openDraft(projectDir, opts.module, brief.rel, opts);
|
|
773
991
|
const notes = listed.length > 0 && !listed.includes(opts.module)
|
|
774
992
|
? [
|
|
775
993
|
(0, diagnostics_1.info)("draft_module_not_in_brief", `${opts.module} is not among the brief's modules (${listed.join(", ")}).`, {
|
|
776
|
-
fix: "Add it to `modules:` in the brief's front matter, so `specgate
|
|
994
|
+
fix: "Add it to `modules:` in the brief's front matter, so `specgate propose --from-brief` lists it.",
|
|
777
995
|
}),
|
|
778
996
|
]
|
|
779
997
|
: [];
|
|
@@ -788,7 +1006,7 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
788
1006
|
reserved: opened.reqRange,
|
|
789
1007
|
created: opened.created,
|
|
790
1008
|
prompt: pasteLine(opts.module),
|
|
791
|
-
next: `specgate
|
|
1009
|
+
next: `specgate propose --from-brief ${opts.module}`,
|
|
792
1010
|
},
|
|
793
1011
|
status: notes,
|
|
794
1012
|
}, () => {
|
|
@@ -800,7 +1018,7 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
800
1018
|
[`${dir}/change.yaml, proposal.md`, ""],
|
|
801
1019
|
];
|
|
802
1020
|
const w = Math.max(...files.map((r) => r[0].length));
|
|
803
|
-
out.write(ui.header(
|
|
1021
|
+
out.write(ui.header(`${this.verb} ${opts.module}`, brief.rel) +
|
|
804
1022
|
ui.ok(`Draft ${ui.bold(opened.changeId)} opened ` +
|
|
805
1023
|
ui.muted(`· REQ ids ${opened.reqRange[0]}…${opened.reqRange[1]} reserved`)) +
|
|
806
1024
|
"\n" +
|
|
@@ -809,7 +1027,8 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
809
1027
|
ui.section("Have your agent write it") +
|
|
810
1028
|
" Paste this into Claude Code, Cursor, Codex…\n\n" +
|
|
811
1029
|
ui.quote(pasteLine(opts.module)) +
|
|
812
|
-
ui.next(`specgate
|
|
1030
|
+
ui.next(`specgate propose --from-brief ${opts.module} --agent "claude -p"`, `or let Specgate call it — then run specgate propose --from-brief ${opts.module} until it says Ready to review`));
|
|
1031
|
+
// Every note above is built with a `fix`.
|
|
813
1032
|
for (const n of notes)
|
|
814
1033
|
out.write(ui.info(n.message) + ui.fix(n.fix) + "\n");
|
|
815
1034
|
});
|
|
@@ -827,7 +1046,7 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
827
1046
|
const guide = readIf(path.join(projectDir, CHANGES, changeId, "DRAFTING.md")) || "";
|
|
828
1047
|
const agent = (0, confined_agent_1.withWritePermission)(opts.agent);
|
|
829
1048
|
if (!opts.json) {
|
|
830
|
-
process.stdout.write(ui.header(
|
|
1049
|
+
process.stdout.write(ui.header(`${this.verb} ${opts.module}`, brief) +
|
|
831
1050
|
(created.length ? ui.ok(`Draft ${changeId} opened, linked to ${brief}.`) : "") +
|
|
832
1051
|
agentLine(agent.command, `is drafting ${opts.module}`, agent.added));
|
|
833
1052
|
}
|
|
@@ -842,7 +1061,7 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
842
1061
|
io.fail({ draft: null }, [
|
|
843
1062
|
(0, diagnostics_1.error)("draft_agent_needs_git", "--agent needs a git repository.", {
|
|
844
1063
|
fix: "Git is how Specgate finds what the agent wrote and undoes anything outside the draft. " +
|
|
845
|
-
"Run `git init`, then `specgate
|
|
1064
|
+
"Run `git init`, then `specgate propose --from-brief " +
|
|
846
1065
|
opts.module +
|
|
847
1066
|
' --agent "…"` again — or paste the prompt into your agent yourself.',
|
|
848
1067
|
}),
|
|
@@ -878,16 +1097,16 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
878
1097
|
state: status.state,
|
|
879
1098
|
brief,
|
|
880
1099
|
prompt: pasteLine(opts.module),
|
|
881
|
-
next: `specgate
|
|
1100
|
+
next: `specgate propose --from-brief ${opts.module}`,
|
|
882
1101
|
},
|
|
883
|
-
}, () => process.stdout.write((afterAgent ? "\n" : ui.header(
|
|
1102
|
+
}, () => process.stdout.write((afterAgent ? "\n" : ui.header(`${this.verb} ${opts.module}`, brief)) +
|
|
884
1103
|
(afterAgent
|
|
885
1104
|
? ui.fail("The agent ran, but the draft is still the placeholder.") +
|
|
886
1105
|
ui.fix(confined_agent_1.NO_WRITE_HINT) +
|
|
887
1106
|
"\n"
|
|
888
1107
|
: " The draft is still the placeholder. Have your agent write it:\n\n" +
|
|
889
1108
|
ui.quote(pasteLine(opts.module)) +
|
|
890
|
-
ui.next(`specgate
|
|
1109
|
+
ui.next(`specgate propose --from-brief ${opts.module} --agent "claude -p"`, "or let Specgate call it"))));
|
|
891
1110
|
process.exitCode = 1;
|
|
892
1111
|
return;
|
|
893
1112
|
}
|
|
@@ -899,7 +1118,9 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
899
1118
|
const report = result.report;
|
|
900
1119
|
const diags = [...report.status, ...skippedNotes(report)];
|
|
901
1120
|
const errors = report.status.length;
|
|
902
|
-
|
|
1121
|
+
// Ready: reviewed, then its requirements are applied one by one, and the
|
|
1122
|
+
// change is archived once they are all delivered (ADR-0035).
|
|
1123
|
+
const next = errors === 0 ? "specgate apply" : `specgate propose --from-brief ${opts.module}`;
|
|
903
1124
|
io.emit({
|
|
904
1125
|
draft: {
|
|
905
1126
|
module: opts.module,
|
|
@@ -915,7 +1136,7 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
915
1136
|
status: diags,
|
|
916
1137
|
}, () => {
|
|
917
1138
|
const out = process.stdout;
|
|
918
|
-
out.write(afterAgent ? "\n" : ui.header(
|
|
1139
|
+
out.write(afterAgent ? "\n" : ui.header(`${this.verb} ${opts.module}`, brief));
|
|
919
1140
|
writeFindings(report, diags);
|
|
920
1141
|
const dir = `${CHANGES.split(path.sep).join("/")}/${changeId}`;
|
|
921
1142
|
const steps = [];
|
|
@@ -923,17 +1144,17 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
923
1144
|
out.write(`${diags.length ? "\n" : ""}` + ui.ok(ui.bold("Ready to review")));
|
|
924
1145
|
steps.push(`Read ${dir}/specs/ with whoever knows the business`, fs.existsSync(path.join(projectDir, CHANGES, changeId, "questions.md"))
|
|
925
1146
|
? `Answer what you can in ${dir}/questions.md`
|
|
926
|
-
: `Check ${dir}/assumptions.md, if the agent wrote one`, next);
|
|
1147
|
+
: `Check ${dir}/assumptions.md, if the agent wrote one`, [next, "its requirements, one at a time; then specgate archive " + changeId]);
|
|
927
1148
|
const after = nextModuleAfter(projectDir, brief, opts.module);
|
|
928
1149
|
steps.push(after
|
|
929
|
-
? [`specgate
|
|
1150
|
+
? [`specgate propose --from-brief ${after}`, "the next module of the brief"]
|
|
930
1151
|
: ["specgate status", "every module drafted: what to build first"]);
|
|
931
1152
|
}
|
|
932
1153
|
else {
|
|
933
1154
|
out.write("\n" +
|
|
934
1155
|
ui.fail(`${ui.bold(`${errors} finding(s) to fix`)} ${ui.muted("— each says how; DRAFTING.md has the formats")}`));
|
|
935
1156
|
steps.push([
|
|
936
|
-
`specgate
|
|
1157
|
+
`specgate propose --from-brief ${opts.module} --agent "claude -p"`,
|
|
937
1158
|
"your agent fixes them — or fix them yourself",
|
|
938
1159
|
], [next, "again, until it says Ready to review"]);
|
|
939
1160
|
}
|
|
@@ -966,7 +1187,7 @@ class DraftCommand extends command_1.BaseCommand {
|
|
|
966
1187
|
status,
|
|
967
1188
|
}, () => {
|
|
968
1189
|
const out = process.stdout;
|
|
969
|
-
out.write(ui.header(
|
|
1190
|
+
out.write(ui.header(`${this.verb} --check ${opts.check}`));
|
|
970
1191
|
writeFindings(report, status);
|
|
971
1192
|
const skipped = report.skipped.length;
|
|
972
1193
|
out.write(errors === 0
|
|
@@ -993,4 +1214,19 @@ function writeFindings(report, status) {
|
|
|
993
1214
|
out.write(ui.fix(d.fix));
|
|
994
1215
|
}
|
|
995
1216
|
}
|
|
1217
|
+
/** The brief rules' findings: code, the module they are about, message, fix. */
|
|
1218
|
+
function writeBriefFindings(findings) {
|
|
1219
|
+
const out = process.stdout;
|
|
1220
|
+
for (const d of findings) {
|
|
1221
|
+
const where = d.target ? `${ui.bold(d.target)} ` : "";
|
|
1222
|
+
const line = `${ui.muted(d.code)} ${where}${d.message}`;
|
|
1223
|
+
out.write(d.severity === "error"
|
|
1224
|
+
? ui.fail(line)
|
|
1225
|
+
: d.severity === "warning"
|
|
1226
|
+
? ui.warn(line)
|
|
1227
|
+
: ui.info(line));
|
|
1228
|
+
if (d.fix)
|
|
1229
|
+
out.write(ui.fix(d.fix));
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
996
1232
|
//# sourceMappingURL=DraftCommand.js.map
|