@moretea-labs/forge 1.7.0 → 1.7.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/AGENTS.md +2 -0
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +2 -0
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +2 -0
- package/assets/plugin-registry.v1.json +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/helpers/check-architecture-sync.sh +17 -0
- package/assets/templates/helpers/inspect-project-state.ts +8 -6
- package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
- package/assets/templates/helpers/workflow-contract.ts +5 -1
- package/docs/architecture/CURRENT.md +12 -4
- package/docs/forge-plugin-management.md +1 -1
- package/docs/operations/controller-performance-and-502.md +19 -0
- package/docs/operations/homebrew.md +2 -2
- package/package.json +5 -2
- package/scripts/architecture-event.ts +2 -820
- package/scripts/architecture-queue.sh +2 -645
- package/scripts/archive-architecture-request.sh +2 -242
- package/scripts/archive-workflow.sh +1 -291
- package/scripts/benchmark-work-efficiency.ts +0 -0
- package/scripts/capability-config.ts +2 -392
- package/scripts/capability-resolver.ts +2 -503
- package/scripts/capture-plan.sh +1 -332
- package/scripts/check-agent-tooling.sh +2 -1531
- package/scripts/check-architecture-sync.sh +2 -302
- package/scripts/check-brain-manifest.sh +2 -253
- package/scripts/check-context-files.sh +2 -47
- package/scripts/check-deploy-sql-order.sh +2 -115
- package/scripts/check-runtime-architecture.mjs +243 -16
- package/scripts/check-skill-version.ts +3 -191
- package/scripts/check-source-duplication.mjs +61 -0
- package/scripts/check-stable-baseline.ts +0 -0
- package/scripts/check-task-sync.sh +2 -63
- package/scripts/check-task-workflow.sh +2 -1000
- package/scripts/codex-handoff-resume.sh +2 -267
- package/scripts/context-contract-sync.sh +2 -514
- package/scripts/contract-run.ts +2 -463
- package/scripts/contract-worktree.sh +1 -702
- package/scripts/ensure-task-workflow.sh +1 -1192
- package/scripts/inspect-project-state.ts +4 -279
- package/scripts/lib/project-init-lib.sh +7 -2
- package/scripts/lib/run-forge-helper.sh +19 -0
- package/scripts/lib/run-forge-helper.ts +26 -0
- package/scripts/migrate-project-template.sh +1 -33
- package/scripts/migrate-workflow-docs.ts +4 -412
- package/scripts/new-plan.sh +1 -180
- package/scripts/new-spec.sh +1 -47
- package/scripts/new-sprint.sh +1 -46
- package/scripts/plan-to-todo.sh +1 -771
- package/scripts/prepare-codex-handoff.sh +2 -201
- package/scripts/prepare-handoff.sh +1 -78
- package/scripts/refresh-current-status.sh +2 -467
- package/scripts/run-governed-gate.ts +3 -0
- package/scripts/select-agent-context-blocks.sh +2 -64
- package/scripts/ship-worktrees.sh +1 -531
- package/scripts/sprint-backlog.sh +1 -834
- package/scripts/stage-runtime-release.ts +2 -1
- package/scripts/summarize-failures.sh +2 -123
- package/scripts/switch-plan.sh +1 -150
- package/scripts/sync-brain-docs.sh +2 -379
- package/scripts/sync-generated-authority.mjs +175 -0
- package/scripts/verify-contract.sh +2 -593
- package/scripts/verify-sprint.sh +1 -500
- package/scripts/workflow-contract.ts +4 -201
- package/scripts/workstream-sync.sh +2 -261
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/commands/plugin.ts +12 -3
- package/src/cli/controller/check-runner.ts +7 -2
- package/src/cli/controller/context-pack.ts +46 -21
- package/src/cli/github/contracts.ts +16 -0
- package/src/cli/github/github.ts +2 -16
- package/src/cli/github/session.ts +2 -1
- package/src/cli/local-bridge/facade-api.ts +37 -47
- package/src/cli/local-bridge/server.ts +2 -2
- package/src/cli/mcp/legacy-tool-service.ts +10 -33
- package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
- package/src/cli/mcp/repository-tools.ts +85 -89
- package/src/cli/mcp/server.ts +57 -13
- package/src/cli/mcp/tool-contract.ts +14 -0
- package/src/cli/mcp/toolset.ts +2 -2
- package/src/cli/mcp/transports/http.ts +23 -0
- package/src/cli/repositories/command-classifier.ts +2 -2
- package/src/cli/repositories/command-executor.ts +24 -6
- package/src/cli/repositories/repository-snapshot.ts +5 -2
- package/src/cli/repositories/structured-git.ts +57 -3
- package/src/cli/repository/inspector.ts +11 -3
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +61 -0
- package/src/runtime/context/semantic-navigation.ts +9 -61
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +18 -25
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
- package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
- package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
- package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
- package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
- package/src/runtime/control-plane/execution/work-handle-store.ts +1 -1
- package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
- package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
- package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
- package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +4 -43
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +13 -3
- package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
- package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
- package/src/runtime/control-plane/facade/controller-round-relay.ts +164 -37
- package/src/runtime/control-plane/facade/controller-session-store.ts +183 -24
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +15 -1
- package/src/runtime/control-plane/facade/goal-workloop.ts +329 -148
- package/src/runtime/control-plane/facade/index.ts +2 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +90 -22
- package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
- package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
- package/src/runtime/control-plane/facade/types.ts +68 -5
- package/src/runtime/control-plane/facade/work-contract-store.ts +3 -209
- package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +14 -7
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +5 -1
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +13 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +15 -6
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +54 -5
- package/src/runtime/control-plane/launcher/thin-launcher.ts +56 -7
- package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
- package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
- package/src/runtime/control-plane/routing/route-policy.ts +18 -6
- package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
- package/src/runtime/evidence/process-check-execution.ts +18 -0
- package/src/runtime/execution/jobs/child-reference.ts +6 -2
- package/src/runtime/execution/process-runtime/check-result.ts +53 -18
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
- package/src/runtime/execution/process-runtime/command-facade.ts +39 -17
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +20 -3
- package/src/runtime/execution/process-runtime/persisted-check.ts +4 -1
- package/src/runtime/execution/process-runtime/runtime.ts +10 -0
- package/src/runtime/execution/process-runtime/types.ts +2 -12
- package/src/runtime/execution/work-activity.ts +21 -0
- package/src/runtime/execution/workers/executor.ts +18 -13
- package/src/runtime/gateway/mcp/execution-tools.ts +35 -2930
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
- package/src/runtime/gateway/mcp/router.ts +2 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +21 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +805 -1009
- package/src/runtime/plugins/app-store-connect-adapter.ts +93 -28
- package/src/runtime/plugins/browser-adapter.ts +256 -1083
- package/src/runtime/plugins/browser-handoff-host.ts +2 -25
- package/src/runtime/plugins/browser-handoff.ts +18 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +121 -16
- package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
- package/src/runtime/plugins/browser-native-session-service.ts +222 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +2 -0
- package/src/runtime/plugins/browser-runtime.ts +10 -0
- package/src/runtime/plugins/browser-session-store.ts +121 -0
- package/src/runtime/plugins/browser-session-types.ts +98 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +4 -4
- package/src/runtime/plugins/external-adapter.ts +92 -58
- package/src/runtime/plugins/macos-capability-broker.ts +0 -8
- package/src/runtime/plugins/store.ts +66 -9
- package/src/runtime/plugins/types.ts +3 -1
- package/src/runtime/recovery/maintenance-executor.ts +61 -25
- package/src/runtime/root/package-connector-service.ts +46 -4
- package/src/runtime/root/package-runtime-release.ts +1 -1
- package/src/runtime/root/package-runtime-service.ts +33 -2
- package/src/runtime/root/release-manifest.ts +137 -16
- package/src/runtime/root/release-materialize.ts +109 -35
- package/src/runtime/root/types.ts +10 -0
- package/src/runtime/standalone-recovery/core.ts +425 -194
- package/src/runtime/standalone-recovery/entry.ts +1 -1
- package/src/runtime/standalone-recovery/installer.ts +1 -1
- package/src/runtime/workflow/schedules/engine.ts +113 -87
- package/src/runtime/workflow/schedules/settlement.ts +26 -23
- package/src/runtime/workflow/schedules/store.ts +79 -10
|
@@ -1,413 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
export * from "../assets/templates/helpers/migrate-workflow-docs";
|
|
3
|
+
import { runTrackedForgeHelper } from "./lib/run-forge-helper";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
type MigrationRecord = {
|
|
7
|
-
source: string;
|
|
8
|
-
target: string;
|
|
9
|
-
action: "archive" | "rewrite" | "append" | "skip";
|
|
10
|
-
note: string;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
type MigrationSummary = {
|
|
14
|
-
repo: string;
|
|
15
|
-
mode: Mode;
|
|
16
|
-
migrated: MigrationRecord[];
|
|
17
|
-
skipped: string[];
|
|
18
|
-
manual_followups: string[];
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
function parseArgs(argv: string[]) {
|
|
22
|
-
let repo = process.cwd();
|
|
23
|
-
let mode: Mode = "dry-run";
|
|
24
|
-
let format: "json" | "text" = "text";
|
|
25
|
-
|
|
26
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
27
|
-
const arg = argv[i];
|
|
28
|
-
if (arg === "--repo") {
|
|
29
|
-
repo = argv[i + 1] ? resolve(argv[i + 1]) : repo;
|
|
30
|
-
i += 1;
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
if (arg === "--apply") {
|
|
34
|
-
mode = "apply";
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
if (arg === "--dry-run") {
|
|
38
|
-
mode = "dry-run";
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (arg === "--format") {
|
|
42
|
-
format = argv[i + 1] === "json" ? "json" : "text";
|
|
43
|
-
i += 1;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return { repo, mode, format };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function ensureDir(path: string, mode: Mode) {
|
|
51
|
-
if (mode === "apply") {
|
|
52
|
-
mkdirSync(path, { recursive: true });
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function appendIfMissing(target: string, marker: string, block: string, mode: Mode) {
|
|
57
|
-
const existing = existsSync(target) ? readFileSync(target, "utf-8") : "";
|
|
58
|
-
if (existing.includes(marker)) return false;
|
|
59
|
-
if (mode === "apply") {
|
|
60
|
-
ensureDir(dirname(target), mode);
|
|
61
|
-
const next = existing ? `${existing.trimEnd()}\n\n${block}\n` : `${block}\n`;
|
|
62
|
-
writeFileSync(target, next);
|
|
63
|
-
}
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function hasCanonicalTodoHeader(content: string): boolean {
|
|
68
|
-
return /^# Deferred Goal Ledger\s*$/m.test(content) && /^\> \*\*Status\*\*:\s*Backlog\s*$/m.test(content);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function writeCanonicalTodo(target: string, mode: Mode) {
|
|
72
|
-
if (existsSync(target)) return;
|
|
73
|
-
const content = [
|
|
74
|
-
"# Deferred Goal Ledger",
|
|
75
|
-
"",
|
|
76
|
-
"> **Status**: Backlog",
|
|
77
|
-
"> **Updated**: (migration)",
|
|
78
|
-
"> **Scope**: Medium/long-term goals deferred from active plan execution",
|
|
79
|
-
"",
|
|
80
|
-
"Current plan tasks live in the active plan's `## Task Breakdown`.",
|
|
81
|
-
"Do not duplicate that execution checklist here. Record only work intentionally deferred beyond this slice, with the tradeoff and revisit trigger.",
|
|
82
|
-
"",
|
|
83
|
-
"## Deferred Goals",
|
|
84
|
-
"",
|
|
85
|
-
"| Goal | Why Deferred | Tradeoff | Revisit Trigger |",
|
|
86
|
-
"|------|--------------|----------|-----------------|",
|
|
87
|
-
"| (none) | No deferred medium/long-term goal recorded yet. | Keep migrated workflow state bounded. | Add a row when a real follow-up is postponed. |",
|
|
88
|
-
].join("\n");
|
|
89
|
-
if (mode === "apply") {
|
|
90
|
-
ensureDir(dirname(target), mode);
|
|
91
|
-
writeFileSync(target, `${content}\n`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function normalizeLegacyTodo(target: string, archivePath: string, mode: Mode) {
|
|
96
|
-
if (!existsSync(target)) return false;
|
|
97
|
-
|
|
98
|
-
const existing = readFileSync(target, "utf-8");
|
|
99
|
-
if (hasCanonicalTodoHeader(existing)) return false;
|
|
100
|
-
|
|
101
|
-
const content = [
|
|
102
|
-
"# Deferred Goal Ledger",
|
|
103
|
-
"",
|
|
104
|
-
"> **Status**: Backlog",
|
|
105
|
-
"> **Updated**: (migration)",
|
|
106
|
-
"> **Scope**: Medium/long-term goals deferred from active plan execution",
|
|
107
|
-
"",
|
|
108
|
-
"Current plan tasks live in the active plan's `## Task Breakdown`.",
|
|
109
|
-
"Do not duplicate that execution checklist here. Record only work intentionally deferred beyond this slice, with the tradeoff and revisit trigger.",
|
|
110
|
-
"",
|
|
111
|
-
"## Deferred Goals",
|
|
112
|
-
"",
|
|
113
|
-
"| Goal | Why Deferred | Tradeoff | Revisit Trigger |",
|
|
114
|
-
"|------|--------------|----------|-----------------|",
|
|
115
|
-
"| Review archived legacy checklist | Legacy tasks/todos.md contained execution checklist content before migration. | Preserve user-authored task text in tasks/archive instead of guessing which items still matter. | Open the archive and promote real follow-up work into a new plan or a deferred-goal row. |",
|
|
116
|
-
].join("\n");
|
|
117
|
-
|
|
118
|
-
if (mode === "apply") {
|
|
119
|
-
ensureDir(dirname(target), mode);
|
|
120
|
-
ensureDir(dirname(archivePath), mode);
|
|
121
|
-
if (!existsSync(archivePath)) {
|
|
122
|
-
writeFileSync(archivePath, `${existing.trimEnd()}\n`);
|
|
123
|
-
}
|
|
124
|
-
writeFileSync(target, `${content}\n`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function migrateLegacySingularTodo(source: string, target: string, archivePath: string, mode: Mode) {
|
|
131
|
-
if (!existsSync(source)) return false;
|
|
132
|
-
|
|
133
|
-
const existing = readFileSync(source, "utf-8");
|
|
134
|
-
const content = hasCanonicalTodoHeader(existing)
|
|
135
|
-
? existing.trimEnd()
|
|
136
|
-
: [
|
|
137
|
-
"# Deferred Goal Ledger",
|
|
138
|
-
"",
|
|
139
|
-
"> **Status**: Backlog",
|
|
140
|
-
"> **Updated**: (migration)",
|
|
141
|
-
"> **Scope**: Medium/long-term goals deferred from active plan execution",
|
|
142
|
-
"",
|
|
143
|
-
"Current plan tasks live in the active plan's `## Task Breakdown`.",
|
|
144
|
-
"Do not duplicate that execution checklist here. Record only work intentionally deferred beyond this slice, with the tradeoff and revisit trigger.",
|
|
145
|
-
"",
|
|
146
|
-
"## Deferred Goals",
|
|
147
|
-
"",
|
|
148
|
-
"| Goal | Why Deferred | Tradeoff | Revisit Trigger |",
|
|
149
|
-
"|------|--------------|----------|-----------------|",
|
|
150
|
-
"| Review archived legacy checklist | Legacy tasks/todo.md contained execution checklist content before migration. | Preserve user-authored task text in tasks/archive instead of guessing which items still matter. | Open the archive and promote real follow-up work into a new plan or a deferred-goal row. |",
|
|
151
|
-
].join("\n");
|
|
152
|
-
|
|
153
|
-
if (mode === "apply") {
|
|
154
|
-
ensureDir(dirname(target), mode);
|
|
155
|
-
ensureDir(dirname(archivePath), mode);
|
|
156
|
-
if (!existsSync(archivePath)) {
|
|
157
|
-
writeFileSync(archivePath, `${existing.trimEnd()}\n`);
|
|
158
|
-
}
|
|
159
|
-
if (!existsSync(target)) {
|
|
160
|
-
writeFileSync(target, `${content}\n`);
|
|
161
|
-
}
|
|
162
|
-
renameSync(source, `${source}.migrated.bak`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function writeResearchReadme(target: string, mode: Mode) {
|
|
169
|
-
if (existsSync(target)) return;
|
|
170
|
-
const content = [
|
|
171
|
-
"# Research Reports",
|
|
172
|
-
"",
|
|
173
|
-
"Durable research reports live in this directory as dated Markdown files.",
|
|
174
|
-
"",
|
|
175
|
-
"Use `YYYYMMDD-topic.md` names for new reports. Keep task-local implementation",
|
|
176
|
-
"decisions in `tasks/notes/`, and keep repeated correction-derived rules in",
|
|
177
|
-
"`tasks/lessons.md`.",
|
|
178
|
-
].join("\n");
|
|
179
|
-
if (mode === "apply") {
|
|
180
|
-
ensureDir(dirname(target), mode);
|
|
181
|
-
writeFileSync(target, `${content}\n`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function migrateLegacyResearch(source: string, archivePath: string, mode: Mode) {
|
|
186
|
-
if (!existsSync(source)) return false;
|
|
187
|
-
|
|
188
|
-
const existing = readFileSync(source, "utf-8");
|
|
189
|
-
if (existing.includes("**Canonical Surface**: `docs/researches/`")) return false;
|
|
190
|
-
|
|
191
|
-
const tombstone = [
|
|
192
|
-
"# Research Notes Moved",
|
|
193
|
-
"",
|
|
194
|
-
"> **Status**: Retired tombstone",
|
|
195
|
-
"> **Canonical Surface**: `docs/researches/`",
|
|
196
|
-
"> **Legacy Archive**: `docs/researches/legacy-research-notes.md`",
|
|
197
|
-
"",
|
|
198
|
-
"Durable research reports now live under `docs/researches/*.md`. This file is",
|
|
199
|
-
"kept only as a transition pointer for older tooling and historical links; do",
|
|
200
|
-
"not add new findings here.",
|
|
201
|
-
].join("\n");
|
|
202
|
-
|
|
203
|
-
if (mode === "apply") {
|
|
204
|
-
ensureDir(dirname(archivePath), mode);
|
|
205
|
-
if (!existsSync(archivePath)) {
|
|
206
|
-
writeFileSync(archivePath, `${existing.trimEnd()}\n`);
|
|
207
|
-
}
|
|
208
|
-
writeFileSync(source, `${tombstone}\n`);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return true;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function writeResearchReport(target: string, marker: string, title: string, body: string, mode: Mode) {
|
|
215
|
-
const block = [`# ${title}`, "", marker, "", body.trimEnd()].join("\n");
|
|
216
|
-
return appendIfMissing(target, marker, block, mode);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export function migrate(repo: string, mode: Mode): MigrationSummary {
|
|
220
|
-
const summary: MigrationSummary = {
|
|
221
|
-
repo,
|
|
222
|
-
mode,
|
|
223
|
-
migrated: [],
|
|
224
|
-
skipped: [],
|
|
225
|
-
manual_followups: [],
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
const planDoc = join(repo, "docs", "plan.md");
|
|
229
|
-
const todoDoc = join(repo, "docs", "TODO.md");
|
|
230
|
-
const progressDoc = join(repo, "docs", "PROGRESS.md");
|
|
231
|
-
const tasksTodo = join(repo, "tasks", "todos.md");
|
|
232
|
-
const legacySingularTasksTodo = join(repo, "tasks", "todo.md");
|
|
233
|
-
const tasksResearch = join(repo, "tasks", "research.md");
|
|
234
|
-
const researchDir = join(repo, "docs", "researches");
|
|
235
|
-
const researchReadme = join(researchDir, "README.md");
|
|
236
|
-
const legacyResearchArchive = join(researchDir, "legacy-research-notes.md");
|
|
237
|
-
const legacyProgressResearch = join(researchDir, "legacy-progress-import.md");
|
|
238
|
-
const plansArchive = join(repo, "plans", "archive");
|
|
239
|
-
const tasksArchive = join(repo, "tasks", "archive");
|
|
240
|
-
const legacyPlanArchive = join(plansArchive, "legacy-docs-plan.md");
|
|
241
|
-
const legacyTodoArchive = join(tasksArchive, "legacy-docs-TODO.md");
|
|
242
|
-
const legacyProgressArchive = join(tasksArchive, "legacy-docs-PROGRESS.md");
|
|
243
|
-
const legacyTasksTodoArchive = join(tasksArchive, "legacy-tasks-todo.md");
|
|
244
|
-
const legacyContractDoc = join(repo, "docs", "contract.md");
|
|
245
|
-
const legacyReviewDoc = join(repo, "docs", "review.md");
|
|
246
|
-
const legacyHandoffDoc = join(repo, "docs", "handoff.md");
|
|
247
|
-
const rootHandoffDoc = join(repo, "HANDOFF.md");
|
|
248
|
-
|
|
249
|
-
ensureDir(plansArchive, mode);
|
|
250
|
-
ensureDir(tasksArchive, mode);
|
|
251
|
-
if (migrateLegacySingularTodo(legacySingularTasksTodo, tasksTodo, legacyTasksTodoArchive, mode)) {
|
|
252
|
-
summary.migrated.push({
|
|
253
|
-
source: "tasks/todo.md",
|
|
254
|
-
target: "tasks/todos.md",
|
|
255
|
-
action: "rewrite",
|
|
256
|
-
note: "Archived legacy singular todo content and normalized tasks/todos.md to the deferred-goal ledger.",
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
writeCanonicalTodo(tasksTodo, mode);
|
|
260
|
-
if (normalizeLegacyTodo(tasksTodo, legacyTasksTodoArchive, mode)) {
|
|
261
|
-
summary.migrated.push({
|
|
262
|
-
source: "tasks/todos.md",
|
|
263
|
-
target: "tasks/todos.md",
|
|
264
|
-
action: "rewrite",
|
|
265
|
-
note: "Archived legacy task checklist content and normalized tasks/todos.md to the deferred-goal ledger.",
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
ensureDir(researchDir, mode);
|
|
269
|
-
writeResearchReadme(researchReadme, mode);
|
|
270
|
-
if (migrateLegacyResearch(tasksResearch, legacyResearchArchive, mode)) {
|
|
271
|
-
summary.migrated.push({
|
|
272
|
-
source: "tasks/research.md",
|
|
273
|
-
target: "docs/researches/legacy-research-notes.md",
|
|
274
|
-
action: "archive",
|
|
275
|
-
note: "Archived legacy singleton research notes and left a tombstone pointer.",
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// NOTE: The "<!-- forge: legacy-docs-import ... -->" markers below are
|
|
280
|
-
// data markers written into migrated repos and used by appendIfMissing as the
|
|
281
|
-
// idempotency key, so renaming them would re-import already-migrated legacy docs.
|
|
282
|
-
// Keep the marker string stable.
|
|
283
|
-
if (existsSync(planDoc)) {
|
|
284
|
-
const content = readFileSync(planDoc, "utf-8");
|
|
285
|
-
const archiveBlock = [
|
|
286
|
-
"# Legacy Plan Import",
|
|
287
|
-
"",
|
|
288
|
-
"<!-- forge: legacy-docs-import docs/plan.md -->",
|
|
289
|
-
"",
|
|
290
|
-
"Original `docs/plan.md` content was archived during migration.",
|
|
291
|
-
"",
|
|
292
|
-
"## Imported Content",
|
|
293
|
-
"",
|
|
294
|
-
content.trimEnd(),
|
|
295
|
-
].join("\n");
|
|
296
|
-
|
|
297
|
-
if (mode === "apply" && !existsSync(legacyPlanArchive)) {
|
|
298
|
-
writeFileSync(legacyPlanArchive, `${archiveBlock}\n`);
|
|
299
|
-
}
|
|
300
|
-
if (mode === "apply") {
|
|
301
|
-
renameSync(planDoc, `${planDoc}.migrated.bak`);
|
|
302
|
-
}
|
|
303
|
-
summary.migrated.push({
|
|
304
|
-
source: "docs/plan.md",
|
|
305
|
-
target: "plans/archive/legacy-docs-plan.md",
|
|
306
|
-
action: "archive",
|
|
307
|
-
note: "Archived uncertain legacy plan content for manual review.",
|
|
308
|
-
});
|
|
309
|
-
summary.manual_followups.push("Review plans/archive/legacy-docs-plan.md and create a canonical plan if the content is still active.");
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (existsSync(todoDoc)) {
|
|
313
|
-
const content = readFileSync(todoDoc, "utf-8").trimEnd();
|
|
314
|
-
const hadCanonicalTodo = existsSync(tasksTodo);
|
|
315
|
-
|
|
316
|
-
if (!hadCanonicalTodo) {
|
|
317
|
-
writeCanonicalTodo(tasksTodo, mode);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (mode === "apply" && !existsSync(legacyTodoArchive)) {
|
|
321
|
-
writeFileSync(legacyTodoArchive, `${content}\n`);
|
|
322
|
-
renameSync(todoDoc, `${todoDoc}.migrated.bak`);
|
|
323
|
-
}
|
|
324
|
-
summary.migrated.push({
|
|
325
|
-
source: "docs/TODO.md",
|
|
326
|
-
target: "tasks/todos.md",
|
|
327
|
-
action: hadCanonicalTodo ? "skip" : "rewrite",
|
|
328
|
-
note: hadCanonicalTodo
|
|
329
|
-
? "Archived the legacy todo without rewriting the existing deferred-goal ledger."
|
|
330
|
-
: "Created the deferred-goal ledger and archived the legacy todo for manual plan triage.",
|
|
331
|
-
});
|
|
332
|
-
summary.manual_followups.push(
|
|
333
|
-
"Review tasks/archive/legacy-docs-TODO.md and promote any still-relevant work into a new plan instead of rehydrating it into tasks/todos.md."
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (existsSync(progressDoc)) {
|
|
338
|
-
const content = readFileSync(progressDoc, "utf-8").trimEnd();
|
|
339
|
-
if (!readFileSync(progressDoc, "utf-8").includes("milestone checkpoints only")) {
|
|
340
|
-
writeResearchReport(
|
|
341
|
-
legacyProgressResearch,
|
|
342
|
-
"<!-- forge: legacy-docs-import docs/PROGRESS.md -->",
|
|
343
|
-
"Legacy Progress Import",
|
|
344
|
-
["Imported from a legacy execution log stored in `docs/PROGRESS.md`.", "", content].join("\n"),
|
|
345
|
-
mode
|
|
346
|
-
);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (mode === "apply") {
|
|
350
|
-
if (!existsSync(legacyProgressArchive)) {
|
|
351
|
-
writeFileSync(legacyProgressArchive, `${content}\n`);
|
|
352
|
-
}
|
|
353
|
-
renameSync(progressDoc, `${progressDoc}.migrated.bak`);
|
|
354
|
-
}
|
|
355
|
-
summary.migrated.push({
|
|
356
|
-
source: "docs/PROGRESS.md",
|
|
357
|
-
target: "tasks/archive/legacy-docs-PROGRESS.md",
|
|
358
|
-
action: "archive",
|
|
359
|
-
note: "Archived legacy progress notes; docs/PROGRESS.md is no longer a generated workflow surface.",
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const archiveDoc = (sourcePath: string, archiveName: string, note: string) => {
|
|
364
|
-
if (!existsSync(sourcePath)) return;
|
|
365
|
-
const target = join(tasksArchive, archiveName);
|
|
366
|
-
if (mode === "apply" && !existsSync(target)) {
|
|
367
|
-
writeFileSync(target, `${readFileSync(sourcePath, "utf-8").trimEnd()}\n`);
|
|
368
|
-
renameSync(sourcePath, `${sourcePath}.migrated.bak`);
|
|
369
|
-
}
|
|
370
|
-
summary.migrated.push({
|
|
371
|
-
source: sourcePath.replace(`${repo}/`, ""),
|
|
372
|
-
target: target.replace(`${repo}/`, ""),
|
|
373
|
-
action: "archive",
|
|
374
|
-
note,
|
|
375
|
-
});
|
|
376
|
-
summary.manual_followups.push(`Review ${target.replace(`${repo}/`, "")} and re-home any still-relevant content.`);
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
archiveDoc(legacyContractDoc, "legacy-docs-contract.md", "Archived legacy contract notes for manual triage.");
|
|
380
|
-
archiveDoc(legacyReviewDoc, "legacy-docs-review.md", "Archived legacy review notes for manual triage.");
|
|
381
|
-
archiveDoc(legacyHandoffDoc, "legacy-docs-handoff.md", "Archived legacy handoff notes for manual triage.");
|
|
382
|
-
archiveDoc(rootHandoffDoc, "legacy-root-HANDOFF.md", "Archived root handoff notes for manual triage.");
|
|
383
|
-
|
|
384
|
-
return summary;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function renderText(summary: MigrationSummary): string {
|
|
388
|
-
const lines = [
|
|
389
|
-
`[migrate-docs] repo: ${summary.repo}`,
|
|
390
|
-
`[migrate-docs] mode: ${summary.mode}`,
|
|
391
|
-
];
|
|
392
|
-
|
|
393
|
-
for (const item of summary.migrated) {
|
|
394
|
-
lines.push(`[migrate-docs] ${item.source} -> ${item.target} (${item.action})`);
|
|
395
|
-
lines.push(`[migrate-docs] note: ${item.note}`);
|
|
396
|
-
}
|
|
397
|
-
for (const followup of summary.manual_followups) {
|
|
398
|
-
lines.push(`[migrate-docs] follow-up: ${followup}`);
|
|
399
|
-
}
|
|
400
|
-
if (summary.migrated.length === 0) {
|
|
401
|
-
lines.push("[migrate-docs] no legacy documents detected");
|
|
402
|
-
}
|
|
403
|
-
return lines.join("\n");
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const { repo, mode, format } = parseArgs(process.argv.slice(2));
|
|
407
|
-
const summary = migrate(repo, mode);
|
|
408
|
-
|
|
409
|
-
if (format === "json") {
|
|
410
|
-
console.log(JSON.stringify(summary, null, 2));
|
|
411
|
-
} else {
|
|
412
|
-
console.log(renderText(summary));
|
|
413
|
-
}
|
|
5
|
+
if (import.meta.main) runTrackedForgeHelper("migrate-workflow-docs.ts");
|
package/scripts/new-plan.sh
CHANGED
|
@@ -1,183 +1,4 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
set -euo pipefail
|
|
3
|
-
|
|
4
3
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
-
|
|
6
|
-
cd "$REPO_ROOT"
|
|
7
|
-
elif [[ "$SCRIPT_DIR" == */.ai/harness/scripts ]]; then
|
|
8
|
-
cd "$SCRIPT_DIR/../../.."
|
|
9
|
-
else
|
|
10
|
-
cd "$SCRIPT_DIR/.."
|
|
11
|
-
fi
|
|
12
|
-
|
|
13
|
-
usage() {
|
|
14
|
-
cat <<'USAGE_EOF'
|
|
15
|
-
Usage: scripts/new-plan.sh --slug <slug> [--title <title>]
|
|
16
|
-
USAGE_EOF
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
normalize_slug() {
|
|
20
|
-
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-{2,}/-/g'
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
escape_sed_replacement() {
|
|
24
|
-
printf '%s' "$1" | sed -e 's/[\/&]/\\&/g'
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
slug=""
|
|
28
|
-
title=""
|
|
29
|
-
|
|
30
|
-
while [[ $# -gt 0 ]]; do
|
|
31
|
-
case "$1" in
|
|
32
|
-
--slug)
|
|
33
|
-
[[ -n "${2:-}" ]] || { echo "Error: --slug requires a value" >&2; usage; exit 1; }
|
|
34
|
-
slug="$2"
|
|
35
|
-
shift 2
|
|
36
|
-
;;
|
|
37
|
-
--title)
|
|
38
|
-
[[ -n "${2:-}" ]] || { echo "Error: --title requires a value" >&2; usage; exit 1; }
|
|
39
|
-
title="$2"
|
|
40
|
-
shift 2
|
|
41
|
-
;;
|
|
42
|
-
--help|-h)
|
|
43
|
-
usage
|
|
44
|
-
exit 0
|
|
45
|
-
;;
|
|
46
|
-
*)
|
|
47
|
-
echo "Unknown argument: $1" >&2
|
|
48
|
-
usage
|
|
49
|
-
exit 1
|
|
50
|
-
;;
|
|
51
|
-
esac
|
|
52
|
-
done
|
|
53
|
-
|
|
54
|
-
if [[ -z "$slug" ]]; then
|
|
55
|
-
echo "--slug is required" >&2
|
|
56
|
-
usage
|
|
57
|
-
exit 1
|
|
58
|
-
fi
|
|
59
|
-
|
|
60
|
-
slug="$(normalize_slug "$slug")"
|
|
61
|
-
if [[ -z "$slug" ]]; then
|
|
62
|
-
echo "Slug is empty after normalization" >&2
|
|
63
|
-
exit 1
|
|
64
|
-
fi
|
|
65
|
-
|
|
66
|
-
if [[ -z "$title" ]]; then
|
|
67
|
-
title="$slug"
|
|
68
|
-
fi
|
|
69
|
-
|
|
70
|
-
timestamp="$(date +%Y%m%d-%H%M)"
|
|
71
|
-
mkdir -p plans plans/archive .claude/templates
|
|
72
|
-
|
|
73
|
-
template_file=".claude/templates/plan.template.md"
|
|
74
|
-
if [[ ! -f "$template_file" ]]; then
|
|
75
|
-
cat > "$template_file" <<'PLAN_TEMPLATE_EOF'
|
|
76
|
-
# Plan: {{TITLE}}
|
|
77
|
-
|
|
78
|
-
> **Status**: Draft
|
|
79
|
-
> **Created**: {{TIMESTAMP}}
|
|
80
|
-
> **Slug**: {{SLUG}}
|
|
81
|
-
> **Spec**: `docs/spec.md`
|
|
82
|
-
> **Research**: See `docs/researches/`
|
|
83
|
-
> **Task Contract**: `tasks/contracts/{{ARTIFACT_STEM}}.contract.md`
|
|
84
|
-
> **Task Review**: `tasks/reviews/{{ARTIFACT_STEM}}.review.md`
|
|
85
|
-
> **Implementation Notes**: `tasks/notes/{{ARTIFACT_STEM}}.notes.md`
|
|
86
|
-
|
|
87
|
-
## Agentic Routing
|
|
88
|
-
- Selected route:
|
|
89
|
-
- Routing reason:
|
|
90
|
-
- Due diligence:
|
|
91
|
-
- P1 map:
|
|
92
|
-
- P2 trace:
|
|
93
|
-
- P3 decision rationale:
|
|
94
|
-
|
|
95
|
-
## Workflow Inventory
|
|
96
|
-
Complete this inventory before implementation. If any line is unknown, keep the plan in Draft and fill it before projection.
|
|
97
|
-
|
|
98
|
-
- Active plan: `{{PLAN_FILE}}`
|
|
99
|
-
- Sprint contract: `tasks/contracts/{{ARTIFACT_STEM}}.contract.md`
|
|
100
|
-
- Sprint review: `tasks/reviews/{{ARTIFACT_STEM}}.review.md`
|
|
101
|
-
- Implementation notes: `tasks/notes/{{ARTIFACT_STEM}}.notes.md`
|
|
102
|
-
- Deferred-goal ledger: `tasks/todos.md`
|
|
103
|
-
- Current checks: `.ai/harness/checks/latest.json`
|
|
104
|
-
- Run snapshots: `.ai/harness/runs/`
|
|
105
|
-
- Scope authority: `tasks/contracts/{{ARTIFACT_STEM}}.contract.md` `allowed_paths`
|
|
106
|
-
- Concurrency rule: `.ai/harness/active-plan` selects the active plan for this worktree when present; `.ai/harness/active-worktree` records the owning worktree; `.claude/.active-plan` is a legacy fallback during transition. If another worktree already owns active work, open or switch to the matching worktree instead of serializing unrelated plans.
|
|
107
|
-
- Execution isolation: approved contract-level work projects through `scripts/plan-to-todo.sh --plan {{PLAN_FILE}}` and may start `scripts/contract-worktree.sh start --plan {{PLAN_FILE}}`.
|
|
108
|
-
|
|
109
|
-
## Approach
|
|
110
|
-
### Strategy
|
|
111
|
-
### Trade-offs
|
|
112
|
-
| Option | Pros | Cons | Decision |
|
|
113
|
-
|--------|------|------|----------|
|
|
114
|
-
|
|
115
|
-
## Detailed Design
|
|
116
|
-
### File Changes
|
|
117
|
-
| File | Action | Description |
|
|
118
|
-
|------|--------|-------------|
|
|
119
|
-
|
|
120
|
-
### Code Snippets
|
|
121
|
-
### Data Flow
|
|
122
|
-
|
|
123
|
-
## Risk Assessment
|
|
124
|
-
| Risk | Likelihood | Impact | Mitigation |
|
|
125
|
-
|------|------------|--------|------------|
|
|
126
|
-
|
|
127
|
-
## Task Contracts
|
|
128
|
-
- Contract file: `tasks/contracts/{{ARTIFACT_STEM}}.contract.md`
|
|
129
|
-
- Review file: `tasks/reviews/{{ARTIFACT_STEM}}.review.md`
|
|
130
|
-
- Implementation notes file: `tasks/notes/{{ARTIFACT_STEM}}.notes.md`
|
|
131
|
-
- Template: `.claude/templates/contract.template.md`
|
|
132
|
-
- Verification command: `bash scripts/verify-contract.sh --contract tasks/contracts/{{ARTIFACT_STEM}}.contract.md --strict`
|
|
133
|
-
- Active plan rule: `.ai/harness/active-plan` is authoritative for this worktree when present; `.ai/harness/active-worktree` records the owning worktree; `.claude/.active-plan` is a legacy fallback during transition. Do not infer active execution from the latest non-archived plan.
|
|
134
|
-
|
|
135
|
-
## Handoff
|
|
136
|
-
|
|
137
|
-
- Checks file: `.ai/harness/checks/latest.json`
|
|
138
|
-
- Session continuation: `.ai/harness/session/continuation.md`
|
|
139
|
-
|
|
140
|
-
## Evidence Contract
|
|
141
|
-
|
|
142
|
-
- **State/progress path**:
|
|
143
|
-
- **Verification evidence**:
|
|
144
|
-
- **Impact review**:
|
|
145
|
-
- **Stop condition**:
|
|
146
|
-
- **Rollback surface**:
|
|
147
|
-
|
|
148
|
-
## Annotations
|
|
149
|
-
<!-- [NOTE]: prefixed inline. Claude processes all and revises. -->
|
|
150
|
-
|
|
151
|
-
## Task Breakdown
|
|
152
|
-
- [ ] ...
|
|
153
|
-
PLAN_TEMPLATE_EOF
|
|
154
|
-
fi
|
|
155
|
-
|
|
156
|
-
base_name="plan-${timestamp}-${slug}.md"
|
|
157
|
-
plan_file="plans/${base_name}"
|
|
158
|
-
counter=2
|
|
159
|
-
while [[ -f "$plan_file" ]]; do
|
|
160
|
-
plan_file="plans/plan-${timestamp}-${slug}-v${counter}.md"
|
|
161
|
-
counter=$((counter + 1))
|
|
162
|
-
done
|
|
163
|
-
artifact_stem="$(basename "$plan_file" .md | sed -E 's/^plan-//')"
|
|
164
|
-
|
|
165
|
-
slug_esc="$(escape_sed_replacement "$slug")"
|
|
166
|
-
artifact_stem_esc="$(escape_sed_replacement "$artifact_stem")"
|
|
167
|
-
title_esc="$(escape_sed_replacement "$title")"
|
|
168
|
-
timestamp_esc="$(escape_sed_replacement "$timestamp")"
|
|
169
|
-
|
|
170
|
-
sed \
|
|
171
|
-
-e "s/{{SLUG}}/${slug_esc}/g" \
|
|
172
|
-
-e "s/{{ARTIFACT_STEM}}/${artifact_stem_esc}/g" \
|
|
173
|
-
-e "s/{{TITLE}}/${title_esc}/g" \
|
|
174
|
-
-e "s/{{TIMESTAMP}}/${timestamp_esc}/g" \
|
|
175
|
-
-e "s|{{PLAN_FILE}}|${plan_file}|g" \
|
|
176
|
-
"$template_file" \
|
|
177
|
-
| sed \
|
|
178
|
-
-e "s|tasks/contracts/${slug_esc}\\.contract\\.md|tasks/contracts/${artifact_stem_esc}.contract.md|g" \
|
|
179
|
-
-e "s|tasks/reviews/${slug_esc}\\.review\\.md|tasks/reviews/${artifact_stem_esc}.review.md|g" \
|
|
180
|
-
-e "s|tasks/notes/${slug_esc}\\.notes\\.md|tasks/notes/${artifact_stem_esc}.notes.md|g" \
|
|
181
|
-
> "$plan_file"
|
|
182
|
-
|
|
183
|
-
echo "Created plan: $plan_file"
|
|
4
|
+
exec "$SCRIPT_DIR/lib/run-forge-helper.sh" "new-plan.sh" "$@"
|
package/scripts/new-spec.sh
CHANGED
|
@@ -1,50 +1,4 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
set -euo pipefail
|
|
3
|
-
|
|
4
3
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
-
|
|
6
|
-
cd "$REPO_ROOT"
|
|
7
|
-
elif [[ "$SCRIPT_DIR" == */.ai/harness/scripts ]]; then
|
|
8
|
-
cd "$SCRIPT_DIR/../../.."
|
|
9
|
-
else
|
|
10
|
-
cd "$SCRIPT_DIR/.."
|
|
11
|
-
fi
|
|
12
|
-
|
|
13
|
-
mkdir -p docs .claude/templates
|
|
14
|
-
|
|
15
|
-
template_file=".claude/templates/spec.template.md"
|
|
16
|
-
if [[ ! -f "$template_file" ]]; then
|
|
17
|
-
cat > "$template_file" <<'SPEC_TEMPLATE_EOF'
|
|
18
|
-
# Product Spec: {{PROJECT_NAME}}
|
|
19
|
-
|
|
20
|
-
> **Status**: Draft
|
|
21
|
-
> **Last Updated**: {{TIMESTAMP}}
|
|
22
|
-
> **Owner**: Planner
|
|
23
|
-
|
|
24
|
-
## Product Outcome
|
|
25
|
-
|
|
26
|
-
Describe the stable user or operator outcome this repo should deliver.
|
|
27
|
-
|
|
28
|
-
## Success Criteria
|
|
29
|
-
|
|
30
|
-
- Primary workflow:
|
|
31
|
-
- Quality bar:
|
|
32
|
-
- Out of scope:
|
|
33
|
-
|
|
34
|
-
## Constraints
|
|
35
|
-
|
|
36
|
-
- Technical:
|
|
37
|
-
- Compliance:
|
|
38
|
-
- Delivery:
|
|
39
|
-
SPEC_TEMPLATE_EOF
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
project_name="$(basename "$PWD")"
|
|
43
|
-
timestamp="$(date '+%Y-%m-%d %H:%M')"
|
|
44
|
-
|
|
45
|
-
sed \
|
|
46
|
-
-e "s/{{PROJECT_NAME}}/${project_name}/g" \
|
|
47
|
-
-e "s/{{TIMESTAMP}}/${timestamp}/g" \
|
|
48
|
-
"$template_file" > docs/spec.md
|
|
49
|
-
|
|
50
|
-
echo "Created docs/spec.md"
|
|
4
|
+
exec "$SCRIPT_DIR/lib/run-forge-helper.sh" "new-spec.sh" "$@"
|