@stdd/plugin 0.9.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.
Files changed (80) hide show
  1. package/.claude-plugin/plugin.json +9 -0
  2. package/.codex-plugin/plugin.json +21 -0
  3. package/LICENSE +21 -0
  4. package/README.md +47 -0
  5. package/extensions/stdd.mjs +77 -0
  6. package/hooks/claude-hooks.json +28 -0
  7. package/hooks/codex-hooks.json +28 -0
  8. package/package.json +38 -0
  9. package/runtime/adapters/README.md +158 -0
  10. package/runtime/cli/check.mjs +555 -0
  11. package/runtime/cli/ci.mjs +190 -0
  12. package/runtime/cli/claude-hooks.mjs +689 -0
  13. package/runtime/cli/config.mjs +27 -0
  14. package/runtime/cli/evidence.mjs +249 -0
  15. package/runtime/cli/generated-files.mjs +1693 -0
  16. package/runtime/cli/held-fs.mjs +415 -0
  17. package/runtime/cli/init.mjs +883 -0
  18. package/runtime/cli/ledger.mjs +1470 -0
  19. package/runtime/cli/lib.mjs +909 -0
  20. package/runtime/cli/path-bytes.mjs +83 -0
  21. package/runtime/cli/policy.mjs +112 -0
  22. package/runtime/cli/recorders.mjs +188 -0
  23. package/runtime/cli/review-fs.mjs +825 -0
  24. package/runtime/cli/review.mjs +1065 -0
  25. package/runtime/cli/runtime.mjs +32 -0
  26. package/runtime/cli/scope.mjs +185 -0
  27. package/runtime/cli/snapshot.mjs +897 -0
  28. package/runtime/cli/state-validation.mjs +168 -0
  29. package/runtime/cli/status.mjs +580 -0
  30. package/runtime/cli/stdd.mjs +536 -0
  31. package/runtime/cli/worker-fs.mjs +971 -0
  32. package/runtime/cli/worker-metadata.mjs +139 -0
  33. package/runtime/cli/worker.mjs +779 -0
  34. package/runtime/method/README.md +634 -0
  35. package/runtime/method/reference-commands.md +147 -0
  36. package/runtime/method/reference-generated-state.md +151 -0
  37. package/runtime/method/reference-integration.md +233 -0
  38. package/runtime/package.json +65 -0
  39. package/runtime/playbooks/brainstorming.md +46 -0
  40. package/runtime/playbooks/debugging.md +36 -0
  41. package/runtime/playbooks/delegate-slice.md +129 -0
  42. package/runtime/playbooks/finish-change.md +46 -0
  43. package/runtime/playbooks/implement.md +26 -0
  44. package/runtime/playbooks/investigation.md +33 -0
  45. package/runtime/playbooks/managed-playbooks.json +14 -0
  46. package/runtime/playbooks/planning.md +177 -0
  47. package/runtime/playbooks/pr-green.md +50 -0
  48. package/runtime/playbooks/start-change.md +37 -0
  49. package/runtime/playbooks/worktrees.md +45 -0
  50. package/runtime/prebuilds/stdd-fs/darwin-arm64/stdd-fs +0 -0
  51. package/runtime/prebuilds/stdd-fs/darwin-x64/stdd-fs +0 -0
  52. package/runtime/prebuilds/stdd-fs/linux-arm64/stdd-fs +0 -0
  53. package/runtime/prebuilds/stdd-fs/linux-x64/stdd-fs +0 -0
  54. package/runtime/prebuilds/stdd-fs/manifest.json +47 -0
  55. package/runtime/prebuilds/stdd-fs/win32-arm64/stdd-fs.exe +0 -0
  56. package/runtime/prebuilds/stdd-fs/win32-x64/stdd-fs.exe +0 -0
  57. package/runtime/sdk/adapters.mjs +279 -0
  58. package/runtime/sdk/file-observation.mjs +12 -0
  59. package/runtime/sdk/index.d.ts +140 -0
  60. package/runtime/sdk/index.mjs +31 -0
  61. package/runtime/sdk/native-fs.mjs +1235 -0
  62. package/runtime/sdk/path.mjs +71 -0
  63. package/runtime/sdk/text.mjs +42 -0
  64. package/runtime/sdk/workflow.mjs +294 -0
  65. package/runtime/templates/deferred-design.md +47 -0
  66. package/runtime/templates/github-stdd.yml +42 -0
  67. package/runtime/templates/gitlab-stdd.yml +72 -0
  68. package/runtime/templates/pr-description.md +35 -0
  69. package/scripts/adopting-root.mjs +42 -0
  70. package/scripts/stdd-hook.mjs +72 -0
  71. package/skills/stdd-brainstorming/SKILL.md +48 -0
  72. package/skills/stdd-debugging/SKILL.md +38 -0
  73. package/skills/stdd-delegate-slice/SKILL.md +118 -0
  74. package/skills/stdd-finish-change/SKILL.md +40 -0
  75. package/skills/stdd-implement/SKILL.md +28 -0
  76. package/skills/stdd-investigation/SKILL.md +35 -0
  77. package/skills/stdd-planning/SKILL.md +165 -0
  78. package/skills/stdd-pr-green/SKILL.md +52 -0
  79. package/skills/stdd-start-change/SKILL.md +39 -0
  80. package/skills/stdd-worktrees/SKILL.md +46 -0
@@ -0,0 +1,32 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { resolveWritableRepoPath } from "../sdk/path.mjs";
3
+
4
+ export function fail(message) {
5
+ console.error(`stdd: ${message}`);
6
+ process.exit(1);
7
+ }
8
+
9
+ export function statePath(cwd, relative, label) {
10
+ try {
11
+ return resolveWritableRepoPath(cwd, relative, label);
12
+ } catch (err) {
13
+ fail(err.message);
14
+ }
15
+ }
16
+
17
+ /** Run git in the working directory, returning trimmed stdout; throws on failure. */
18
+ export function git(...args) {
19
+ return execFileSync("git", args, {
20
+ encoding: "utf8",
21
+ stdio: ["ignore", "pipe", "pipe"],
22
+ }).trim();
23
+ }
24
+
25
+ // cap on a subprocess's captured stdout — large diffs, manifests, and
26
+ // command output must not truncate silently at execFile's small default
27
+ export const MAX_SUBPROCESS_BUFFER = 64 * 1024 * 1024;
28
+
29
+ // the first line of a subprocess error's stderr or message — the actual
30
+ // cause (ENOENT, permission, maxBuffer overflow), not a guessed diagnosis
31
+ export const subprocessError = (err) =>
32
+ (err?.stderr?.toString().trim() || err?.message || "unknown error").split("\n")[0];
@@ -0,0 +1,185 @@
1
+ // --- declared scope: validation, glob classification, and postflight ---
2
+ //
3
+ // Owns the scope contract shared by delegated slices and managed workers: what
4
+ // a legal --frozen/--allowed declaration is, how a path is classified against
5
+ // it, plus scope declaration (`stdd slice new`) and postflight (`stdd scope`).
6
+ // It has no dependency on the entry module.
7
+ import { execFileSync } from "node:child_process";
8
+ import { assertPrintableSingleLine } from "../sdk/text.mjs";
9
+ import {
10
+ appendLedger,
11
+ isStateExemptPath,
12
+ ledgerAppendContext,
13
+ loadLedger,
14
+ requireBranch,
15
+ withCapturedLedgerIdentity,
16
+ } from "./ledger.mjs";
17
+ import { latinGlob, pathForMatch, splitNul, viewPath } from "./path-bytes.mjs";
18
+ import { fail, MAX_SUBPROCESS_BUFFER } from "./runtime.mjs";
19
+ import { dirtySnapshot, workerDirtySnapshot } from "./snapshot.mjs";
20
+ import { workerPathForMatch, workerViewPath } from "./worker-fs.mjs";
21
+ import { readWorkerMetadata } from "./worker-metadata.mjs";
22
+
23
+ export function validateScopeDeclaration(subject, frozenPaths, allowedPaths) {
24
+ if (frozenPaths.length === 0 && allowedPaths.length === 0) {
25
+ fail(`${subject} needs a scope — pass --frozen <globs> and/or --allowed <globs>`);
26
+ }
27
+ for (const [label, globs] of [
28
+ ["--frozen", frozenPaths],
29
+ ["--allowed", allowedPaths],
30
+ ]) {
31
+ for (const glob of globs) {
32
+ try {
33
+ assertPrintableSingleLine(glob, `${label} glob`);
34
+ } catch {
35
+ fail(
36
+ `${label} glob must be a non-empty single printable line without control or invisible characters`,
37
+ );
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ function classifyScopePath(frozen, allowed, comparable) {
44
+ if (frozen.some((pattern) => pattern.test(comparable))) return "frozen";
45
+ if (allowed.length > 0 && !allowed.some((pattern) => pattern.test(comparable))) {
46
+ return "outside-allowed";
47
+ }
48
+ return null;
49
+ }
50
+
51
+ export function workerScopeViolations(scope, relativePaths) {
52
+ const frozen = scope.frozenPaths.map(latinGlob);
53
+ const allowed = scope.allowedPaths.map(latinGlob);
54
+ return relativePaths.flatMap((relative) => {
55
+ const kind = classifyScopePath(frozen, allowed, workerPathForMatch(relative));
56
+ return kind ? [{ relative, kind }] : [];
57
+ });
58
+ }
59
+
60
+ /**
61
+ * `stdd slice new` — declare a delegated slice's scope and snapshot the
62
+ * checkout baseline (head + dirty-file hashes) into a ledger scope event.
63
+ */
64
+ export function sliceNew(cwd, frozenPaths, allowedPaths) {
65
+ validateScopeDeclaration("slice new", frozenPaths, allowedPaths);
66
+ const sliceContext = ledgerAppendContext(cwd, { event: "scope" });
67
+ const head = execFileSync("git", ["-C", cwd, "rev-parse", "HEAD"], {
68
+ encoding: "utf8",
69
+ stdio: ["ignore", "pipe", "pipe"],
70
+ }).trim();
71
+ const event = {
72
+ event: "scope",
73
+ frozenPaths,
74
+ allowedPaths,
75
+ baseline: { head, dirty: dirtySnapshot(cwd) },
76
+ ...(sliceContext.task ? { taskId: sliceContext.task.id } : {}),
77
+ };
78
+ try {
79
+ withCapturedLedgerIdentity(
80
+ cwd,
81
+ {
82
+ expectedBranch: sliceContext.branch,
83
+ expectedTaskState: sliceContext.taskState,
84
+ subject: "slice scope",
85
+ retry: "stdd slice new",
86
+ },
87
+ () =>
88
+ appendLedger(cwd, event, {
89
+ preserveTaskScope: true,
90
+ lockHeld: true,
91
+ expectedBranch: sliceContext.branch,
92
+ }),
93
+ );
94
+ } catch (err) {
95
+ fail(err.message);
96
+ }
97
+ console.log(
98
+ `stdd slice: scope declared (frozen: ${frozenPaths.join(", ") || "—"}; ` +
99
+ `allowed: ${allowedPaths.join(", ") || "—"}) — baseline at ${head.slice(0, 7)}`,
100
+ );
101
+ }
102
+
103
+ /** Check sandbox-local changes against the scope bound in worker metadata. */
104
+ function workerScopeCheck(metadata) {
105
+ let dirty;
106
+ try {
107
+ dirty = workerDirtySnapshot(metadata.root, metadata);
108
+ } catch (err) {
109
+ fail(err.message);
110
+ }
111
+ const violations = workerScopeViolations(metadata.scope, Object.keys(dirty)).map(
112
+ ({ relative, kind }) =>
113
+ `${workerViewPath(relative)}: ${kind === "frozen" ? "frozen path modified by this worker" : "outside the allowed paths"}`,
114
+ );
115
+ if (violations.length > 0) {
116
+ console.error(`stdd scope: ${violations.length} violation(s)\n`);
117
+ for (const violation of violations) console.error(` ${violation}`);
118
+ process.exit(1);
119
+ }
120
+ console.log(`stdd scope: OK (${Object.keys(dirty).length} worker change(s), all in scope)`);
121
+ }
122
+
123
+ /**
124
+ * `stdd scope` — postflight against the slice baseline, not a ref. Only
125
+ * session-introduced changes count; inherited dirt (already modified at
126
+ * baseline, byte-identical now) is reported separately, never blamed.
127
+ */
128
+ export function scopeCheck(cwd) {
129
+ const worker = readWorkerMetadata(cwd);
130
+ if (worker) return workerScopeCheck(worker);
131
+ const branch = requireBranch(cwd);
132
+ const scope = loadLedger(cwd, branch)
133
+ .filter((e) => e.event === "scope")
134
+ .at(-1);
135
+ if (!scope) {
136
+ fail("no slice declared for this branch — run `stdd slice new --frozen/--allowed` first");
137
+ }
138
+ let committed;
139
+ try {
140
+ // -z + raw bytes: keep committed paths byte-exact so they compare and
141
+ // glob-match consistently with the latin1 keys from dirtySnapshot
142
+ committed = splitNul(
143
+ execFileSync("git", ["-C", cwd, "diff", "--name-only", "-z", scope.baseline.head, "HEAD"], {
144
+ stdio: ["ignore", "pipe", "pipe"],
145
+ maxBuffer: MAX_SUBPROCESS_BUFFER,
146
+ }),
147
+ ).map(pathForMatch);
148
+ } catch {
149
+ fail(`baseline commit ${scope.baseline.head.slice(0, 7)} is gone — cannot diff the slice`);
150
+ }
151
+ const introduced = new Set(committed);
152
+ const inherited = [];
153
+ const currentDirty = dirtySnapshot(cwd);
154
+ const baselineDirty = scope.baseline.dirty;
155
+ for (const p of new Set([...Object.keys(baselineDirty), ...Object.keys(currentDirty)])) {
156
+ const baselineHas = Object.hasOwn(baselineDirty, p);
157
+ const currentHas = Object.hasOwn(currentDirty, p);
158
+ if (baselineHas && currentHas && baselineDirty[p] === currentDirty[p]) inherited.push(p);
159
+ else introduced.add(p);
160
+ }
161
+ // stdd's own bookkeeping (the ledger grows during the slice by design;
162
+ // the exact trusted reset temp may exist during recovery) is never in scope.
163
+ // Every other .stdd path is an ordinary deliverable and stays visible.
164
+ for (const p of introduced) {
165
+ if (isStateExemptPath(cwd, p)) introduced.delete(p);
166
+ }
167
+ const frozen = scope.frozenPaths.map(latinGlob);
168
+ const allowed = scope.allowedPaths.map(latinGlob);
169
+ const violations = [];
170
+ for (const p of introduced) {
171
+ const shown = viewPath(p);
172
+ const kind = classifyScopePath(frozen, allowed, p);
173
+ if (kind === "frozen") violations.push(`${shown}: frozen path modified by this slice`);
174
+ else if (kind === "outside-allowed") violations.push(`${shown}: outside the allowed paths`);
175
+ }
176
+ for (const p of inherited) {
177
+ console.log(`inherited dirt (present at baseline, not introduced by this slice): ${viewPath(p)}`);
178
+ }
179
+ if (violations.length > 0) {
180
+ console.error(`stdd scope: ${violations.length} violation(s)\n`);
181
+ for (const v of violations) console.error(` ${v}`);
182
+ process.exit(1);
183
+ }
184
+ console.log(`stdd scope: OK (${introduced.size} introduced change(s), all in scope)`);
185
+ }