@pmelab/gtd 23.2.0 → 24.1.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/.claude-plugin/plugin.json +1 -1
- package/README.md +44 -11
- package/claude/hooks/access.ts +1 -1
- package/dist/chunks/Schema-Dh-mkwkq.mjs +25353 -0
- package/dist/chunks/reserved-ejn-LVYO.mjs +1388 -0
- package/dist/chunks/rolldown-runtime-8H4AJuhK.mjs +14 -0
- package/dist/chunks/steering-D-PvRC5W.mjs +16296 -0
- package/dist/chunks/wire-DA7q2ikx.mjs +473 -0
- package/dist/flows.mjs +525 -0
- package/dist/gtd.bundle.mjs +43830 -56044
- package/dist/steering.mjs +2 -0
- package/dist/testkit.mjs +13006 -0
- package/dist/wire.mjs +2 -0
- package/dist/workflow-kit.mjs +352 -0
- package/dist/workflows/feature.mjs +315 -0
- package/dist/workflows/fix.mjs +40 -0
- package/dist/workflows/review.mjs +25 -0
- package/dist/workflows.mjs +1953 -1724
- package/package.json +70 -8
- package/skills/authoring/SKILL.md +5 -4
- package/src/cli/Cli.test.ts +1329 -0
- package/src/cli/Cli.ts +1348 -0
- package/src/cli/Program.test.ts +2262 -0
- package/src/cli/Program.ts +1331 -0
- package/src/cli/ToolCommands.ts +43 -0
- package/src/cli/index.ts +14 -0
- package/src/edge/Edge.test.ts +1016 -0
- package/src/edge/Edge.ts +1225 -0
- package/src/edge/index.ts +16 -0
- package/src/emit/Emit.test.ts +425 -0
- package/src/emit/Emit.ts +139 -0
- package/src/emit/GitScript.test.ts +260 -0
- package/src/emit/GitScript.ts +131 -0
- package/src/emit/ModeContradiction.test.ts +120 -0
- package/src/emit/ModeContradiction.ts +66 -0
- package/src/emit/OutcomeScript.test.ts +238 -0
- package/src/emit/OutcomeScript.ts +73 -0
- package/src/emit/SteeringMode.test.ts +274 -0
- package/src/emit/SteeringMode.ts +256 -0
- package/src/emit/index.ts +40 -0
- package/src/flows/factory.ts +5 -1
- package/src/flows/runtime.ts +3 -0
- package/src/install/ConfigScaffold.ts +19 -0
- package/src/install/Install.test.ts +426 -0
- package/src/install/Install.ts +634 -0
- package/src/install/Version.ts +27 -0
- package/src/install/index.ts +3 -0
- package/src/platform/CommandRunner.ts +74 -0
- package/src/platform/Commentary.test.ts +126 -0
- package/src/platform/Commentary.ts +92 -0
- package/src/platform/Git.test.ts +50 -0
- package/src/platform/Git.ts +552 -0
- package/src/platform/Host.ts +42 -0
- package/src/platform/RetainedHistory.test.ts +65 -0
- package/src/platform/RetainedHistory.ts +34 -0
- package/src/platform/Workspace.test.ts +669 -0
- package/src/platform/Workspace.ts +631 -0
- package/src/platform/WorktreeState.test.ts +162 -0
- package/src/platform/WorktreeState.ts +50 -0
- package/src/platform/index.ts +22 -0
- package/src/replay/Access.test.ts +53 -0
- package/src/replay/Access.ts +46 -0
- package/src/replay/Format.test.ts +20 -0
- package/src/replay/Format.ts +14 -0
- package/src/replay/Replay.test.ts +887 -0
- package/src/replay/Replay.ts +669 -0
- package/src/replay/Trailers.test.ts +117 -0
- package/src/replay/Trailers.ts +213 -0
- package/src/replay/Tree.test.ts +25 -0
- package/src/replay/Tree.ts +45 -0
- package/src/replay/index.ts +11 -0
- package/src/steering/Blocks.fixture.ts +6 -0
- package/src/steering/Blocks.test.ts +167 -0
- package/src/steering/Blocks.ts +321 -0
- package/src/steering/CodeThreads.test.ts +127 -0
- package/src/steering/CodeThreads.ts +197 -0
- package/src/steering/Edits.test.ts +19 -0
- package/src/steering/Edits.ts +20 -0
- package/src/steering/Eol.test.ts +16 -0
- package/src/steering/Eol.ts +2 -0
- package/src/steering/FORMATS.md +58 -0
- package/src/steering/Footnotes.test.ts +977 -0
- package/src/steering/Footnotes.ts +661 -0
- package/src/steering/Inline.fixture.ts +7 -0
- package/src/steering/Inline.test.ts +227 -0
- package/src/steering/Inline.ts +195 -0
- package/src/steering/MarkdownTree.test.ts +247 -0
- package/src/steering/MarkdownTree.ts +260 -0
- package/src/steering/Outline.ts +19 -0
- package/src/steering/SteeringFormat.test.ts +91 -0
- package/src/steering/SteeringFormat.ts +267 -0
- package/src/steering/SteeringFormats.test.ts +163 -0
- package/src/steering/Threads.ts +163 -0
- package/src/steering/contract.test.ts +264 -0
- package/src/steering/fixtures/code-threads/cache.stripped.ts.txt +6 -0
- package/src/steering/fixtures/code-threads/cache.ts.md +15 -0
- package/src/steering/fixtures/free-form/notes.md +12 -0
- package/src/steering/fixtures/free-form/notes.read.json +42 -0
- package/src/steering/fixtures/qa/answered.md +23 -0
- package/src/steering/fixtures/qa/answered.read.json +119 -0
- package/src/steering/fixtures/qa/requirements.md +160 -0
- package/src/steering/fixtures/qa/requirements.read.json +475 -0
- package/src/steering/fixtures/review/review.md +76 -0
- package/src/steering/fixtures/review/review.read.json +237 -0
- package/src/steering/fixtures/review/ticked.md +23 -0
- package/src/steering/fixtures/review/ticked.read.json +99 -0
- package/src/steering/fixtures/review-notes/baseline.md +14 -0
- package/src/steering/fixtures/review-notes/noted.md +19 -0
- package/src/steering/freeform.test.ts +311 -0
- package/src/steering/freeform.ts +148 -0
- package/src/steering/index.test.ts +62 -0
- package/src/steering/index.ts +140 -0
- package/src/steering/qa.test.ts +2160 -0
- package/src/steering/qa.ts +1104 -0
- package/src/steering/review.test.ts +1829 -0
- package/src/steering/review.ts +1180 -0
- package/src/step/LandStep.ts +31 -0
- package/src/step/RepoSnapshot.ts +29 -0
- package/src/step/accessRefusal.test.ts +58 -0
- package/src/step/accessRefusal.ts +26 -0
- package/src/step/index.ts +5 -0
- package/src/step/planStart.test.ts +202 -0
- package/src/step/planStart.ts +167 -0
- package/src/step/planStep.test.ts +104 -0
- package/src/step/planStep.ts +89 -0
- package/src/step/snapshot.fixture.ts +28 -0
- package/src/testkit/EmittedScriptRecognizer.test.ts +639 -0
- package/src/testkit/EmittedScriptRecognizer.ts +642 -0
- package/src/testkit/FakeGitOperations.ts +95 -0
- package/src/testkit/GitTiers.test.ts +11 -0
- package/src/testkit/GitTiers.ts +1052 -0
- package/src/testkit/InMemRepo.ts +467 -0
- package/src/testkit/Layers.test.ts +58 -0
- package/src/testkit/Layers.ts +241 -0
- package/src/testkit/StrictGitOperations.ts +31 -0
- package/src/testkit/TestRepo.test.ts +119 -0
- package/src/testkit/TestRepo.ts +274 -0
- package/src/testkit/cliIo.ts +36 -0
- package/src/testkit/index.ts +9 -0
- package/src/testkit/steps.ts +177 -0
- package/src/testkit/testkit.feature +65 -0
- package/src/wire/BeatDocument.test.ts +419 -0
- package/src/wire/BeatDocument.ts +114 -0
- package/src/wire/BeatPlain.test.ts +113 -0
- package/src/wire/BeatPlain.ts +127 -0
- package/src/wire/BeatStatus.test.ts +59 -0
- package/src/wire/BeatStatus.ts +81 -0
- package/src/wire/Demand.test.ts +142 -0
- package/src/wire/Demand.ts +108 -0
- package/src/wire/ExitCodes.test.ts +21 -0
- package/src/wire/ExitCodes.ts +16 -0
- package/src/wire/Glob.ts +29 -0
- package/src/wire/Judge.ts +22 -0
- package/src/wire/Land.test.ts +120 -0
- package/src/wire/Land.ts +75 -0
- package/src/wire/Select.test.ts +148 -0
- package/src/wire/Select.ts +78 -0
- package/src/wire/Sessions.test.ts +59 -0
- package/src/wire/Sessions.ts +39 -0
- package/src/wire/WireSchema.test.ts +162 -0
- package/src/wire/constants.ts +22 -0
- package/src/wire/golden/1/beat-capture.json +34 -0
- package/src/wire/golden/1/beat-message.json +34 -0
- package/src/wire/golden/1/beat-prompt.json +44 -0
- package/src/wire/golden/1/beat-script.json +34 -0
- package/src/wire/golden/1/beat-stalled.json +34 -0
- package/src/wire/golden/1/judge.json +14 -0
- package/src/wire/golden/1/land-noop.json +10 -0
- package/src/wire/golden/1/land.json +10 -0
- package/src/wire/index.ts +12 -0
- package/src/wire/types.ts +46 -0
- package/src/workflow/Diagnostic.test.ts +93 -0
- package/src/workflow/Diagnostic.ts +54 -0
- package/src/workflow/SteeringFormats.test.ts +27 -0
- package/src/workflow/SteeringFormats.ts +6 -0
- package/src/workflow/Workflow.ts +77 -0
- package/src/workflow/compile.test.ts +53 -0
- package/src/workflow/compile.ts +56 -0
- package/src/workflow/discovery.ts +122 -0
- package/src/workflow/fingerprint.test.ts +52 -0
- package/src/workflow/fingerprint.ts +53 -0
- package/src/workflow/index.ts +33 -0
- package/src/workflow/load.test.ts +1017 -0
- package/src/workflow/load.ts +836 -0
- package/src/workflow/skills.test.ts +109 -0
- package/src/workflow/skills.ts +90 -0
- package/src/workflow/vars.ts +33 -0
- package/src/workflow-kit/health.test.ts +30 -0
- package/src/workflow-kit/health.ts +86 -0
- package/src/workflow-kit/index.ts +7 -0
- package/src/workflow-kit/quality.test.ts +17 -0
- package/src/workflow-kit/quality.ts +45 -0
- package/src/workflow-kit/review.ts +324 -0
- package/src/{workflows → workflow-kit}/scenarios.ts +21 -28
- package/src/workflows/feature/feature.test.ts +32 -0
- package/src/workflows/{bundled.ts → feature/feature.ts} +11 -63
- package/src/workflows/feature/index.ts +10 -0
- package/src/workflows/{packages.test.ts → feature/packages.test.ts} +2 -2
- package/src/workflows/{packages.ts → feature/packages.ts} +3 -3
- package/src/workflows/{planning.ts → feature/planning.ts} +2 -3
- package/src/workflows/{review.test.ts → feature/review.test.ts} +9 -44
- package/src/workflows/feature/review.ts +106 -0
- package/src/workflows/feature/steps.test.ts +82 -0
- package/src/workflows/feature/steps.ts +92 -0
- package/src/workflows/fix/fix.feature +80 -0
- package/src/workflows/fix/fix.test.ts +74 -0
- package/src/workflows/fix/fix.ts +52 -0
- package/src/workflows/fix/index.ts +6 -0
- package/src/workflows/index.ts +36 -2
- package/src/workflows/review/index.ts +6 -0
- package/src/workflows/review/review.test.ts +99 -0
- package/src/workflows/review/review.ts +33 -0
- package/src/workflows/{access.test.ts → shared/access.test.ts} +1 -1
- package/src/workflows/{access.ts → shared/access.ts} +3 -5
- package/src/workflows/shared/config.fixture.ts +12 -0
- package/src/workflows/{health.test.ts → shared/health.test.ts} +1 -1
- package/src/workflows/{health.ts → shared/health.ts} +38 -55
- package/src/workflows/{options.test.ts → shared/options.test.ts} +2 -2
- package/src/workflows/{options.ts → shared/options.ts} +14 -2
- package/src/workflows/shared/reserved.ts +11 -0
- package/src/workflows/{scenarios.test.ts → shared/scenarios.test.ts} +1 -1
- package/src/workflows/shared/scenarios.ts +35 -0
- package/src/workflows/{skills.test.ts → shared/skills.test.ts} +3 -1
- package/src/workflows/{skills.ts → shared/skills.ts} +7 -6
- package/src/workflows/{steps.test.ts → shared/steps.test.ts} +1 -66
- package/src/workflows/{steps.ts → shared/steps.ts} +8 -98
- package/src/workflows/shared/tail.test.ts +205 -0
- package/src/workflows/shared/tail.ts +135 -0
- package/src/workflows/{text.fixture.ts → shared/text.fixture.ts} +2 -2
- package/src/workflows/{text.ts → shared/text.ts} +48 -7
- package/src/workflows/{threads.test.ts → shared/threads.test.ts} +1 -1
- package/src/workflows/shared/workflows.steps.ts +7 -0
- package/src/workflows/review.ts +0 -362
- /package/src/workflows/{prose.ts → shared/prose.ts} +0 -0
- /package/src/workflows/{text.test.ts → shared/text.test.ts} +0 -0
- /package/src/workflows/{vars.ts → shared/vars.ts} +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Zero imports on purpose: a leaf both `Edge.ts` and this module's siblings
|
|
2
|
+
// can import, so `"unspecified"` exists exactly once in the codebase.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The top-level `schema` of every JSON document a driver reads. Bumped only
|
|
6
|
+
* when a field is removed or renamed — never for an added one — and never tied
|
|
7
|
+
* to the package version, since a driver can be installed apart from gtd.
|
|
8
|
+
*/
|
|
9
|
+
export const WIRE_SCHEMA = 1
|
|
10
|
+
|
|
11
|
+
/** The bucket a cost with no `--model` tag is grouped under, kept distinct so a mixed history still totals correctly. */
|
|
12
|
+
export const UNATTRIBUTED_MODEL = "unspecified"
|
|
13
|
+
|
|
14
|
+
/** The plain-text twin of `OutcomeScript.ts`'s `printfLine`. Lives here so `src/wire/`'s encoders reach it without importing out of the leaf. */
|
|
15
|
+
export const renderFormat = (fmt: string, ...args: readonly string[]): string => {
|
|
16
|
+
let i = 0
|
|
17
|
+
return fmt.replace(/%s/g, () => args[i++] ?? "")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The first line of the landing refusal for a turn that wrote outside its `write` access — a stable marker drivers and tests grep for. */
|
|
21
|
+
export const ACCESS_REFUSAL =
|
|
22
|
+
"gtd land: this turn wrote outside its write access — revert these paths, then run `gtd land` again:"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 1,
|
|
3
|
+
"kind": "capture",
|
|
4
|
+
"content": "fix it",
|
|
5
|
+
"idle": false,
|
|
6
|
+
"initial": false,
|
|
7
|
+
"model": "opus",
|
|
8
|
+
"system": "You are a careful senior engineer.",
|
|
9
|
+
"log": ".git/gtd-loop.log",
|
|
10
|
+
"state": "build.fixing",
|
|
11
|
+
"actor": "agent",
|
|
12
|
+
"workflow": "feature",
|
|
13
|
+
"label": "Fixing",
|
|
14
|
+
"memory": "build#a1b2c3d",
|
|
15
|
+
"file": "TODO.md",
|
|
16
|
+
"mode": "qa",
|
|
17
|
+
"changes": [
|
|
18
|
+
{
|
|
19
|
+
"status": "M",
|
|
20
|
+
"path": "TODO.md"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"next": {
|
|
24
|
+
"target": "idle"
|
|
25
|
+
},
|
|
26
|
+
"cost": 12,
|
|
27
|
+
"costByModel": [
|
|
28
|
+
{
|
|
29
|
+
"model": "opus",
|
|
30
|
+
"cost": 12
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"judge": "{\"schema\":1,\"state\":{\"diff\":\"…\"},\"questions\":[{\"id\":\"q1\",\"primitive\":\"noul\",\"instructions\":\"Is the change done?\",\"criteria\":\"yes: every item ticked\"}]}"
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 1,
|
|
3
|
+
"kind": "message",
|
|
4
|
+
"content": "fix it",
|
|
5
|
+
"idle": false,
|
|
6
|
+
"initial": false,
|
|
7
|
+
"model": "opus",
|
|
8
|
+
"system": "You are a careful senior engineer.",
|
|
9
|
+
"log": ".git/gtd-loop.log",
|
|
10
|
+
"state": "build.fixing",
|
|
11
|
+
"actor": "agent",
|
|
12
|
+
"workflow": "feature",
|
|
13
|
+
"label": "Fixing",
|
|
14
|
+
"memory": "build#a1b2c3d",
|
|
15
|
+
"file": "TODO.md",
|
|
16
|
+
"mode": "qa",
|
|
17
|
+
"changes": [
|
|
18
|
+
{
|
|
19
|
+
"status": "M",
|
|
20
|
+
"path": "TODO.md"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"next": {
|
|
24
|
+
"target": "idle"
|
|
25
|
+
},
|
|
26
|
+
"cost": 12,
|
|
27
|
+
"costByModel": [
|
|
28
|
+
{
|
|
29
|
+
"model": "opus",
|
|
30
|
+
"cost": 12
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"judge": "{\"schema\":1,\"state\":{\"diff\":\"…\"},\"questions\":[{\"id\":\"q1\",\"primitive\":\"noul\",\"instructions\":\"Is the change done?\",\"criteria\":\"yes: every item ticked\"}]}"
|
|
34
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 1,
|
|
3
|
+
"kind": "prompt",
|
|
4
|
+
"content": "fix it",
|
|
5
|
+
"idle": false,
|
|
6
|
+
"initial": false,
|
|
7
|
+
"session": {
|
|
8
|
+
"id": "8f2c",
|
|
9
|
+
"resume": true
|
|
10
|
+
},
|
|
11
|
+
"model": "opus",
|
|
12
|
+
"system": "You are a careful senior engineer.",
|
|
13
|
+
"validate": "gtd check qa 'TODO.md'",
|
|
14
|
+
"log": ".git/gtd-loop.log",
|
|
15
|
+
"state": "build.fixing",
|
|
16
|
+
"actor": "agent",
|
|
17
|
+
"workflow": "feature",
|
|
18
|
+
"label": "Fixing",
|
|
19
|
+
"memory": "build#a1b2c3d",
|
|
20
|
+
"file": "TODO.md",
|
|
21
|
+
"mode": "qa",
|
|
22
|
+
"changes": [
|
|
23
|
+
{
|
|
24
|
+
"status": "M",
|
|
25
|
+
"path": "TODO.md"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"next": {
|
|
29
|
+
"target": "idle"
|
|
30
|
+
},
|
|
31
|
+
"cost": 12,
|
|
32
|
+
"costByModel": [
|
|
33
|
+
{
|
|
34
|
+
"model": "opus",
|
|
35
|
+
"cost": 12
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"judge": "{\"schema\":1,\"state\":{\"diff\":\"…\"},\"questions\":[{\"id\":\"q1\",\"primitive\":\"noul\",\"instructions\":\"Is the change done?\",\"criteria\":\"yes: every item ticked\"}]}",
|
|
39
|
+
"skills": ["code-review"],
|
|
40
|
+
"access": {
|
|
41
|
+
"read": null,
|
|
42
|
+
"write": [".gtd/TODO.md"]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 1,
|
|
3
|
+
"kind": "script",
|
|
4
|
+
"content": "fix it",
|
|
5
|
+
"idle": false,
|
|
6
|
+
"initial": false,
|
|
7
|
+
"model": "opus",
|
|
8
|
+
"system": "You are a careful senior engineer.",
|
|
9
|
+
"log": ".git/gtd-loop.log",
|
|
10
|
+
"state": "build.fixing",
|
|
11
|
+
"actor": "agent",
|
|
12
|
+
"workflow": "feature",
|
|
13
|
+
"label": "Fixing",
|
|
14
|
+
"memory": "build#a1b2c3d",
|
|
15
|
+
"file": "TODO.md",
|
|
16
|
+
"mode": "qa",
|
|
17
|
+
"changes": [
|
|
18
|
+
{
|
|
19
|
+
"status": "M",
|
|
20
|
+
"path": "TODO.md"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"next": {
|
|
24
|
+
"target": "idle"
|
|
25
|
+
},
|
|
26
|
+
"cost": 12,
|
|
27
|
+
"costByModel": [
|
|
28
|
+
{
|
|
29
|
+
"model": "opus",
|
|
30
|
+
"cost": 12
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"judge": "{\"schema\":1,\"state\":{\"diff\":\"…\"},\"questions\":[{\"id\":\"q1\",\"primitive\":\"noul\",\"instructions\":\"Is the change done?\",\"criteria\":\"yes: every item ticked\"}]}"
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 1,
|
|
3
|
+
"kind": "stalled",
|
|
4
|
+
"content": "stalled at \"build.fixing\": the last gtd(agent): build.fixing turn landed an empty attempt, the tree is clean, and another dispatch would repeat it.\n\nTwo ways out:\n - sharpen the step's prompt so the turn has something concrete to author\n - pass allowEmpty: true to the step, so an empty turn completes it and the\n flow decides what follows — finishing, or an escalation step after N\n empty turns\n",
|
|
5
|
+
"idle": false,
|
|
6
|
+
"initial": false,
|
|
7
|
+
"model": "opus",
|
|
8
|
+
"system": "You are a careful senior engineer.",
|
|
9
|
+
"log": ".git/gtd-loop.log",
|
|
10
|
+
"state": "build.fixing",
|
|
11
|
+
"actor": "agent",
|
|
12
|
+
"workflow": "feature",
|
|
13
|
+
"label": "Fixing",
|
|
14
|
+
"memory": "build#a1b2c3d",
|
|
15
|
+
"file": "TODO.md",
|
|
16
|
+
"mode": "qa",
|
|
17
|
+
"changes": [
|
|
18
|
+
{
|
|
19
|
+
"status": "M",
|
|
20
|
+
"path": "TODO.md"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"next": {
|
|
24
|
+
"target": "idle"
|
|
25
|
+
},
|
|
26
|
+
"cost": 12,
|
|
27
|
+
"costByModel": [
|
|
28
|
+
{
|
|
29
|
+
"model": "opus",
|
|
30
|
+
"cost": 12
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"judge": "{\"schema\":1,\"state\":{\"diff\":\"…\"},\"questions\":[{\"id\":\"q1\",\"primitive\":\"noul\",\"instructions\":\"Is the change done?\",\"criteria\":\"yes: every item ticked\"}]}"
|
|
34
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./constants.js"
|
|
2
|
+
export * from "./ExitCodes.js"
|
|
3
|
+
export * from "./types.js"
|
|
4
|
+
export * from "./Demand.js"
|
|
5
|
+
export * from "./BeatStatus.js"
|
|
6
|
+
export * from "./BeatDocument.js"
|
|
7
|
+
export * from "./BeatPlain.js"
|
|
8
|
+
export * from "./Land.js"
|
|
9
|
+
export * from "./Judge.js"
|
|
10
|
+
export { selectPath } from "./Select.js"
|
|
11
|
+
export { resolveSession } from "./Sessions.js"
|
|
12
|
+
export { globMatches } from "./Glob.js"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Zero imports on purpose — this is the wire's own vocabulary tier, so
|
|
2
|
+
// `wire` stays a leaf every other module may import.
|
|
3
|
+
|
|
4
|
+
/** No closed vocabulary of "kinds" — any workflow-defined string. */
|
|
5
|
+
export type Actor = string
|
|
6
|
+
|
|
7
|
+
/** Defined by whatever keys `WorkflowDefinition.states` declares — not a closed vocabulary. */
|
|
8
|
+
export type StateName = string
|
|
9
|
+
|
|
10
|
+
/** The three content kinds a state can carry — exactly one per state. */
|
|
11
|
+
export type ContentKind = "script" | "prompt" | "message"
|
|
12
|
+
|
|
13
|
+
/** The name of a steering-file mode. Not a closed vocabulary: the valid set derives from the active definition (`BUILT_IN_MODES` plus whatever `modes:` declares). */
|
|
14
|
+
export type StateMode = string
|
|
15
|
+
|
|
16
|
+
/** One model's summed token cost — the shape `gtd summary`'s template iterates as `it.processCostByModel`, and `gtd next --json`'s `costByModel` entries. */
|
|
17
|
+
export interface ModelCost {
|
|
18
|
+
readonly model: string
|
|
19
|
+
readonly cost: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The subset of `Edge.ts`'s `RenderedRest` the beat builders below read,
|
|
24
|
+
* expressed structurally so this module never imports `RenderedRest` itself
|
|
25
|
+
* — that import (even type-only) would be a Sheriff edge back to root.
|
|
26
|
+
* `RenderedRest` satisfies this shape today and must keep doing so.
|
|
27
|
+
*/
|
|
28
|
+
export interface StepAccess {
|
|
29
|
+
readonly read: readonly string[] | null
|
|
30
|
+
readonly write: readonly string[] | null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface RenderedDemandSource {
|
|
34
|
+
readonly state: StateName
|
|
35
|
+
readonly actor: Actor
|
|
36
|
+
readonly content: string
|
|
37
|
+
readonly model?: string
|
|
38
|
+
readonly system?: string
|
|
39
|
+
readonly label?: string
|
|
40
|
+
readonly memory?: string
|
|
41
|
+
readonly file?: string
|
|
42
|
+
readonly mode?: StateMode
|
|
43
|
+
readonly judge?: string
|
|
44
|
+
readonly skills?: readonly string[]
|
|
45
|
+
readonly access?: StepAccess
|
|
46
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import {
|
|
3
|
+
compareConfigPaths,
|
|
4
|
+
dedupeDiagnostics,
|
|
5
|
+
formatDiagnostic,
|
|
6
|
+
sortDiagnostics,
|
|
7
|
+
type Diagnostic,
|
|
8
|
+
} from "./Diagnostic.js"
|
|
9
|
+
|
|
10
|
+
const diag = (over: Partial<Diagnostic>): Diagnostic => ({
|
|
11
|
+
severity: "error",
|
|
12
|
+
message: "boom",
|
|
13
|
+
path: [],
|
|
14
|
+
origin: "/repo/gtd.config.ts",
|
|
15
|
+
...over,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe("compareConfigPaths", () => {
|
|
19
|
+
it("orders numeric segments numerically, not lexicographically", () => {
|
|
20
|
+
expect(compareConfigPaths(["on", 2], ["on", 10])).toBeLessThan(0)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it("orders string segments lexicographically", () => {
|
|
24
|
+
expect(compareConfigPaths(["a"], ["b"])).toBeLessThan(0)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it("orders a shorter path before a longer one sharing a common prefix", () => {
|
|
28
|
+
expect(
|
|
29
|
+
compareConfigPaths(["machines", "design"], ["machines", "design", "states"]),
|
|
30
|
+
).toBeLessThan(0)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it("is zero for identical paths", () => {
|
|
34
|
+
expect(compareConfigPaths(["machines", "design"], ["machines", "design"])).toBe(0)
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe("sortDiagnostics", () => {
|
|
39
|
+
it("sorts by config path", () => {
|
|
40
|
+
const a = diag({ path: ["on", 2] })
|
|
41
|
+
const b = diag({ path: ["on", 10] })
|
|
42
|
+
expect(sortDiagnostics([b, a])).toEqual([a, b])
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe("dedupeDiagnostics", () => {
|
|
47
|
+
it("collapses two findings identical in all four fields to one", () => {
|
|
48
|
+
const first = diag({ origin: "/repo/gtd.config.ts" })
|
|
49
|
+
const second = diag({ origin: "/repo/gtd.config.ts" })
|
|
50
|
+
expect(dedupeDiagnostics([first, second])).toEqual([first])
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it("keeps two findings that differ only by path", () => {
|
|
54
|
+
const a = diag({ path: ["a"] })
|
|
55
|
+
const b = diag({ path: ["b"] })
|
|
56
|
+
expect(dedupeDiagnostics([a, b])).toEqual([a, b])
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it("keeps two findings that differ only by severity", () => {
|
|
60
|
+
const a = diag({ severity: "error" })
|
|
61
|
+
const b = diag({ severity: "warning" })
|
|
62
|
+
expect(dedupeDiagnostics([a, b])).toEqual([a, b])
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe("formatDiagnostic", () => {
|
|
67
|
+
it("renders `<origin>: <config.path>: <message>`", () => {
|
|
68
|
+
const d = diag({
|
|
69
|
+
origin: "/repo/gtd.config.ts",
|
|
70
|
+
path: ["machines", "root", "states", "idle", "on", "* **"],
|
|
71
|
+
message: 'state "idle": "on" target "nowhere" is not a defined state',
|
|
72
|
+
})
|
|
73
|
+
expect(formatDiagnostic(d)).toBe(
|
|
74
|
+
'/repo/gtd.config.ts: machines.root.states.idle.on.* **: state "idle": "on" target "nowhere" is not a defined state',
|
|
75
|
+
)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it("renders a top-level (path-less) finding with a `(top level)` path placeholder", () => {
|
|
79
|
+
const d = diag({
|
|
80
|
+
origin: "/repo/gtd.config.ts",
|
|
81
|
+
path: [],
|
|
82
|
+
message: "unknown top-level key(s) foo",
|
|
83
|
+
})
|
|
84
|
+
expect(formatDiagnostic(d)).toBe(
|
|
85
|
+
"/repo/gtd.config.ts: (top level): unknown top-level key(s) foo",
|
|
86
|
+
)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it("joins a numeric path segment the same as a string one", () => {
|
|
90
|
+
const d = diag({ origin: "/repo/gtd.config.ts", path: ["on", 2], message: "boom" })
|
|
91
|
+
expect(formatDiagnostic(d)).toBe("/repo/gtd.config.ts: on.2: boom")
|
|
92
|
+
})
|
|
93
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One config-compile finding. `path` mirrors the raw config's own JSON shape
|
|
3
|
+
* (e.g. `["machines","design","states","triage","on",0]`) so a user can find
|
|
4
|
+
* exactly where to fix it; `origin` is the file it came from.
|
|
5
|
+
*/
|
|
6
|
+
export interface Diagnostic {
|
|
7
|
+
readonly severity: "error" | "warning"
|
|
8
|
+
readonly message: string
|
|
9
|
+
readonly path: readonly (string | number)[]
|
|
10
|
+
readonly origin: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const compareSegment = (a: string | number, b: string | number): number => {
|
|
14
|
+
if (typeof a === "number" && typeof b === "number") return a - b
|
|
15
|
+
if (typeof a === "number") return -1
|
|
16
|
+
if (typeof b === "number") return 1
|
|
17
|
+
return a < b ? -1 : a > b ? 1 : 0
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Element-wise path comparison, numbers ordered numerically so `["on", 2]` precedes `["on", 10]`. */
|
|
21
|
+
export const compareConfigPaths = (
|
|
22
|
+
a: readonly (string | number)[],
|
|
23
|
+
b: readonly (string | number)[],
|
|
24
|
+
): number => {
|
|
25
|
+
const len = Math.min(a.length, b.length)
|
|
26
|
+
for (let i = 0; i < len; i++) {
|
|
27
|
+
const c = compareSegment(a[i]!, b[i]!)
|
|
28
|
+
if (c !== 0) return c
|
|
29
|
+
}
|
|
30
|
+
return a.length - b.length
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Sort by config path, so a report lists findings in the order of the file's shape. */
|
|
34
|
+
export const sortDiagnostics = (diagnostics: readonly Diagnostic[]): Diagnostic[] =>
|
|
35
|
+
[...diagnostics].sort((a, b) => compareConfigPaths(a.path, b.path))
|
|
36
|
+
|
|
37
|
+
/** One finding, printable: `<origin>: <path>: <message>` — the structured replacement for the old prose bullet list. */
|
|
38
|
+
export const formatDiagnostic = (d: Diagnostic): string => {
|
|
39
|
+
const path = d.path.length > 0 ? d.path.join(".") : "(top level)"
|
|
40
|
+
return `${d.origin}: ${path}: ${d.message}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Dedup by `(severity, path, message, origin)`, first occurrence wins. */
|
|
44
|
+
export const dedupeDiagnostics = (diagnostics: readonly Diagnostic[]): Diagnostic[] => {
|
|
45
|
+
const seen = new Set<string>()
|
|
46
|
+
const out: Diagnostic[] = []
|
|
47
|
+
for (const d of diagnostics) {
|
|
48
|
+
const key = JSON.stringify([d.severity, d.path, d.message, d.origin])
|
|
49
|
+
if (seen.has(key)) continue
|
|
50
|
+
seen.add(key)
|
|
51
|
+
out.push(d)
|
|
52
|
+
}
|
|
53
|
+
return out
|
|
54
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import fc from "fast-check"
|
|
3
|
+
import { isSeededValidateCommand, seededValidateCommand } from "./SteeringFormats.js"
|
|
4
|
+
import { BUILT_IN_MODE_NAMES } from "../steering/index.js"
|
|
5
|
+
|
|
6
|
+
describe("seededValidateCommand / isSeededValidateCommand", () => {
|
|
7
|
+
it("recognizes its own seeded command for every built-in mode", () => {
|
|
8
|
+
fc.assert(
|
|
9
|
+
fc.property(fc.constantFrom(...BUILT_IN_MODE_NAMES), (mode) => {
|
|
10
|
+
expect(isSeededValidateCommand(mode, seededValidateCommand(mode))).toBe(true)
|
|
11
|
+
}),
|
|
12
|
+
)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it("rejects a hand-written command", () => {
|
|
16
|
+
expect(isSeededValidateCommand("qa", "npx prettier --check $GTD_FILE")).toBe(false)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it("rejects another mode's seeded command", () => {
|
|
20
|
+
expect(isSeededValidateCommand("review", seededValidateCommand("qa"))).toBe(false)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it("rejects near-miss whitespace/quoting variants", () => {
|
|
24
|
+
expect(isSeededValidateCommand("qa", "gtd check qa '$GTD_FILE'")).toBe(false)
|
|
25
|
+
expect(isSeededValidateCommand("qa", "gtd check qa $GTD_FILE")).toBe(false)
|
|
26
|
+
})
|
|
27
|
+
})
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** The unrendered `validate:` template a workflow compiler seeds for a built-in mode — quoted so a path with spaces survives interpolation. */
|
|
2
|
+
export const seededValidateCommand = (mode: string): string => `gtd check ${mode} "$GTD_FILE"`
|
|
3
|
+
|
|
4
|
+
/** True when `command` is exactly `mode`'s seeded template (compared literally, not rendered) — distinguishes gtd's own seeding from a user override. */
|
|
5
|
+
export const isSeededValidateCommand = (mode: string, command: string): boolean =>
|
|
6
|
+
command === seededValidateCommand(mode)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { WorkflowBase, Flow, ScopeAccess, Summary } from "../flows/index.js"
|
|
2
|
+
import type { Actor, ContentKind, StateMode, StateName, StepAccess } from "../wire/index.js"
|
|
3
|
+
|
|
4
|
+
export type { Actor, ContentKind, StateMode, StateName }
|
|
5
|
+
|
|
6
|
+
export type ChangeStatus = "A" | "M" | "D"
|
|
7
|
+
|
|
8
|
+
export interface PendingChange {
|
|
9
|
+
readonly status: ChangeStatus
|
|
10
|
+
readonly path: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* One steering-file mode: shell commands (reading the file from `$GTD_FILE`) that
|
|
15
|
+
* format and validate a file of this format. Both run at the edge, never in
|
|
16
|
+
* the engine.
|
|
17
|
+
*/
|
|
18
|
+
export interface ModeDef {
|
|
19
|
+
readonly format?: string
|
|
20
|
+
readonly validate?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The rest a process waits at, described for the edge: who acts, its one
|
|
25
|
+
* content kind, and the steering-file options the guards and the wire read.
|
|
26
|
+
* Built from the step replay reached — never authored.
|
|
27
|
+
*/
|
|
28
|
+
export interface StepDef {
|
|
29
|
+
readonly actor: Actor
|
|
30
|
+
readonly kind: ContentKind
|
|
31
|
+
readonly content: string
|
|
32
|
+
readonly label?: string
|
|
33
|
+
readonly file?: string
|
|
34
|
+
readonly mode?: StateMode
|
|
35
|
+
readonly model?: string
|
|
36
|
+
readonly system?: string
|
|
37
|
+
readonly judge?: string
|
|
38
|
+
readonly requireProgress?: boolean
|
|
39
|
+
readonly answerGate?: boolean
|
|
40
|
+
readonly requireRevert?: boolean
|
|
41
|
+
readonly allowEmpty?: boolean
|
|
42
|
+
readonly acceptClean?: boolean
|
|
43
|
+
readonly skills?: readonly string[]
|
|
44
|
+
readonly access?: StepAccess
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The loaded workflow module: its default export is the flow, and `summary`
|
|
49
|
+
* and `base` are the optional named exports the engine reads. Every other
|
|
50
|
+
* export is the module's own business — helpers other workflows import.
|
|
51
|
+
*/
|
|
52
|
+
export interface WorkflowDefinition {
|
|
53
|
+
readonly flow: Flow
|
|
54
|
+
readonly summary?: Summary | undefined
|
|
55
|
+
readonly base?: WorkflowBase | undefined
|
|
56
|
+
/** Steering files by path, with their mode — what the LSP knows before a step declaring one is reached. */
|
|
57
|
+
readonly steering: Readonly<Record<string, StateMode>>
|
|
58
|
+
/** Every mode a step may name: the built-in registry merged with `config.modes`. */
|
|
59
|
+
readonly modes: Readonly<Record<StateMode, ModeDef>>
|
|
60
|
+
/** Bundled skill lists keyed by scope full name, given the process's resolved vars — the fallback beneath `configuredSkills` and a `scope()` option. */
|
|
61
|
+
readonly skills: (
|
|
62
|
+
vars: Readonly<Record<string, string>>,
|
|
63
|
+
) => Readonly<Record<string, readonly string[]>>
|
|
64
|
+
/** `config.skills` entries by scope full name; they outrank a `scope()` option, which outranks `skills`. Their keys are checked against `skills` once the process's settings are known. */
|
|
65
|
+
readonly configuredSkills: Readonly<Record<string, readonly string[]>>
|
|
66
|
+
/** Bundled access keyed by scope full name, given the process's resolved vars — the fallback beneath `configuredAccess` and a `scope()` option. */
|
|
67
|
+
readonly access: (vars: Readonly<Record<string, string>>) => Readonly<Record<string, ScopeAccess>>
|
|
68
|
+
/** `config.access` entries by scope full name; they outrank a `scope()` option, which outranks `access`. */
|
|
69
|
+
readonly configuredAccess: Readonly<Record<string, ScopeAccess>>
|
|
70
|
+
/** The file the `skills`, `access` and `config` exports came from, for error reports. */
|
|
71
|
+
readonly skillsOrigin: string
|
|
72
|
+
/** The flow's first step on an ordinary start — where a finished episode waits. */
|
|
73
|
+
readonly initial: StateName
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const knownModes = (def: Pick<WorkflowDefinition, "modes">): readonly StateMode[] =>
|
|
77
|
+
Object.keys(def.modes)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import { compileConfig } from "./compile.js"
|
|
3
|
+
import { seededValidateCommand } from "./index.js"
|
|
4
|
+
|
|
5
|
+
const FILE = "/repo/gtd.config.ts"
|
|
6
|
+
|
|
7
|
+
describe("compileConfig", () => {
|
|
8
|
+
it("compiles no config export to the built-in modes alone", () => {
|
|
9
|
+
expect(compileConfig(undefined, FILE)).toEqual({
|
|
10
|
+
modes: {
|
|
11
|
+
qa: { validate: seededValidateCommand("qa") },
|
|
12
|
+
review: { validate: seededValidateCommand("review") },
|
|
13
|
+
},
|
|
14
|
+
skills: {},
|
|
15
|
+
access: {},
|
|
16
|
+
diagnostics: [],
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it("layers a mode over a built-in one per half, and adds a new one", () => {
|
|
21
|
+
const { modes } = compileConfig(
|
|
22
|
+
{ modes: { qa: { format: "fmt" }, adr: { validate: "v" } } },
|
|
23
|
+
FILE,
|
|
24
|
+
)
|
|
25
|
+
expect(modes["qa"]).toEqual({ format: "fmt", validate: seededValidateCommand("qa") })
|
|
26
|
+
expect(modes["adr"]).toEqual({ validate: "v" })
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("keeps a built-in half a mode leaves undefined", () => {
|
|
30
|
+
const { modes } = compileConfig({ modes: { qa: { format: "fmt", validate: undefined } } }, FILE)
|
|
31
|
+
expect(modes["qa"]).toEqual({ format: "fmt", validate: seededValidateCommand("qa") })
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("reports every problem at the file, under config, and compiles nothing else", () => {
|
|
35
|
+
const compiled = compileConfig({ vars: {}, skills: { a: "x" } }, FILE)
|
|
36
|
+
expect(compiled.skills).toEqual({})
|
|
37
|
+
expect(compiled.diagnostics).toEqual([
|
|
38
|
+
{ severity: "error", path: ["config", "vars"], message: "is not a known key", origin: FILE },
|
|
39
|
+
{
|
|
40
|
+
severity: "error",
|
|
41
|
+
path: ["config", "skills", "a"],
|
|
42
|
+
message: "must be an array of skill names",
|
|
43
|
+
origin: FILE,
|
|
44
|
+
},
|
|
45
|
+
])
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it("keeps a __proto__ skills key as a plain entry", () => {
|
|
49
|
+
const skills = JSON.parse('{ "__proto__": ["x"], "build": ["y"] }') as Record<string, string[]>
|
|
50
|
+
const compiled = compileConfig({ skills }, FILE)
|
|
51
|
+
expect(Object.keys(compiled.skills).sort()).toEqual(["__proto__", "build"])
|
|
52
|
+
})
|
|
53
|
+
})
|