@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,190 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { dedupeChecks } from "./lib.mjs";
3
+ import { fail, git } from "./runtime.mjs";
4
+
5
+ /**
6
+ * The branch's PR as `stdd status` reports it. Degrades, never errors:
7
+ * no gh on PATH or an unexpected gh failure → state "unknown".
8
+ */
9
+ export function statusPr(cwd) {
10
+ let raw;
11
+ try {
12
+ raw = execFileSync("gh", ["pr", "view", "--json", "number,url,statusCheckRollup"], {
13
+ cwd,
14
+ encoding: "utf8",
15
+ stdio: ["ignore", "pipe", "pipe"],
16
+ });
17
+ } catch (err) {
18
+ if (err.code === "ENOENT") return { state: "unknown", reason: "gh unavailable" };
19
+ const stderr = err.stderr?.toString() ?? "";
20
+ if (/no pull requests found/i.test(stderr)) return { state: "none" };
21
+ return {
22
+ state: "unknown",
23
+ reason: stderr.trim().split("\n")[0] || "gh failed",
24
+ };
25
+ }
26
+ let info;
27
+ try {
28
+ info = JSON.parse(raw);
29
+ } catch {
30
+ return { state: "unknown", reason: "gh returned unparseable output" };
31
+ }
32
+ const checks = { success: 0, failure: 0, pending: 0 };
33
+ for (const check of info.statusCheckRollup ?? []) {
34
+ const conclusion = (check.conclusion ?? "").toUpperCase();
35
+ if (conclusion === "SUCCESS" || conclusion === "NEUTRAL" || conclusion === "SKIPPED") {
36
+ checks.success++;
37
+ } else if (conclusion === "") {
38
+ checks.pending++;
39
+ } else {
40
+ checks.failure++;
41
+ }
42
+ }
43
+ return { state: "open", number: info.number, url: info.url, checks };
44
+ }
45
+
46
+ /** One rollup entry → { name, terminal, ok }, for check runs and statuses. */
47
+ function normalizeCheck(entry) {
48
+ const startedAt = entry.startedAt ?? entry.createdAt ?? "";
49
+ if (entry.state !== undefined && entry.status === undefined) {
50
+ const state = (entry.state ?? "").toUpperCase();
51
+ return {
52
+ name: entry.context ?? "unknown",
53
+ terminal: state !== "" && state !== "EXPECTED" && state !== "PENDING",
54
+ ok: state === "SUCCESS",
55
+ startedAt,
56
+ };
57
+ }
58
+ const conclusion = (entry.conclusion ?? "").toUpperCase();
59
+ return {
60
+ name: entry.name ?? "unknown",
61
+ terminal: (entry.status ?? "").toUpperCase() === "COMPLETED",
62
+ ok: conclusion === "SUCCESS" || conclusion === "NEUTRAL" || conclusion === "SKIPPED",
63
+ startedAt,
64
+ };
65
+ }
66
+
67
+ /** The PR's checks for its current head, straight from the forge. */
68
+ function ghPrChecks(pr) {
69
+ const args = ["pr", "view", ...(pr ? [pr] : []), "--json", "number,url,headRefOid,statusCheckRollup"];
70
+ let raw;
71
+ try {
72
+ raw = execFileSync("gh", args, {
73
+ encoding: "utf8",
74
+ stdio: ["ignore", "pipe", "pipe"],
75
+ });
76
+ } catch (err) {
77
+ if (err.code === "ENOENT") fail("stdd ci needs the GitHub CLI (gh) on PATH");
78
+ const stderr = err.stderr?.toString() ?? "";
79
+ if (/no pull requests found/i.test(stderr)) {
80
+ fail("no PR for the current branch — open it first, then watch it settle");
81
+ }
82
+ fail(`gh pr view failed: ${stderr.trim().split("\n")[0] || err.message}`);
83
+ }
84
+ let info;
85
+ try {
86
+ info = JSON.parse(raw);
87
+ } catch {
88
+ fail("gh returned unparseable output");
89
+ }
90
+ return {
91
+ number: info.number,
92
+ url: info.url,
93
+ head: info.headRefOid,
94
+ checks: dedupeChecks((info.statusCheckRollup ?? []).map(normalizeCheck)),
95
+ };
96
+ }
97
+
98
+ /**
99
+ * `stdd ci [pr] [--watch]` — the PR's checks on its **current head**. The
100
+ * watch settles only when the check set is stable (two consecutive polls
101
+ * with the same names) and fully terminal: a watcher attached right after
102
+ * a push sees a partial set — the classic early-settle trap. A terminal
103
+ * failure exits immediately; a head move (amend, force-push) restarts the
104
+ * watch on the new head.
105
+ */
106
+ export async function ciCommand(pr, watch, intervalSec, timeoutSec) {
107
+ const render = (c) => ` ${c.terminal ? (c.ok ? "✓" : "✗") : "…"} ${c.name}`;
108
+ let localHead = null;
109
+ try {
110
+ localHead = git("rev-parse", "HEAD");
111
+ } catch {
112
+ // outside a git repo gh still resolves an explicit PR number
113
+ }
114
+ let lastHead = null;
115
+ let lastNames = null;
116
+ let lastProgress = null;
117
+ let stable = 0;
118
+ const deadline = Date.now() + timeoutSec * 1000;
119
+ for (;;) {
120
+ const view = ghPrChecks(pr);
121
+ if (view.head !== lastHead) {
122
+ if (lastHead === null) {
123
+ console.log(`PR #${view.number} — checks on head ${view.head.slice(0, 7)} (${view.url})`);
124
+ if (localHead && localHead !== view.head) {
125
+ console.error(
126
+ `stdd ci: local HEAD ${localHead.slice(0, 7)} differs from the PR head — ` +
127
+ "watching the PR head; push if you meant your local commit",
128
+ );
129
+ }
130
+ } else {
131
+ console.log(`head moved to ${view.head.slice(0, 7)} — restarting the watch`);
132
+ }
133
+ lastHead = view.head;
134
+ lastNames = null;
135
+ stable = 0;
136
+ }
137
+ const failed = view.checks.filter((c) => c.terminal && !c.ok);
138
+ const allTerminal = view.checks.every((c) => c.terminal);
139
+ const names = view.checks
140
+ .map((c) => c.name)
141
+ .sort()
142
+ .join("\n");
143
+
144
+ if (!watch) {
145
+ for (const c of view.checks) console.log(render(c));
146
+ if (view.checks.length === 0) console.log(" (no checks reported)");
147
+ if (failed.length > 0) {
148
+ console.error(`stdd ci: ${failed.length} failing on ${view.head.slice(0, 7)}`);
149
+ process.exit(1);
150
+ }
151
+ if (allTerminal && view.checks.length > 0) {
152
+ console.log(`stdd ci: green (${view.checks.length} checks) on ${view.head.slice(0, 7)}`);
153
+ return;
154
+ }
155
+ console.error(
156
+ "stdd ci: not settled — checks pending or not yet registered; `stdd ci --watch` waits it out",
157
+ );
158
+ process.exit(1);
159
+ }
160
+
161
+ if (failed.length > 0) {
162
+ for (const c of view.checks) console.log(render(c));
163
+ console.error(`stdd ci: ${failed.length} failing check(s) on ${view.head.slice(0, 7)}`);
164
+ process.exit(1);
165
+ }
166
+ stable = allTerminal && view.checks.length > 0 && names === lastNames ? stable + 1 : 0;
167
+ lastNames = names;
168
+ if (stable >= 1 && allTerminal) {
169
+ // second consecutive all-terminal poll with an unchanged set
170
+ for (const c of view.checks) console.log(render(c));
171
+ console.log(
172
+ `stdd ci: green (${view.checks.length} checks) on ${view.head.slice(0, 7)} — terminal`,
173
+ );
174
+ return;
175
+ }
176
+ const progress = ` ${view.checks.filter((c) => c.terminal).length}/${view.checks.length} terminal`;
177
+ if (progress !== lastProgress) {
178
+ console.log(progress);
179
+ lastProgress = progress;
180
+ }
181
+ if (Date.now() > deadline) {
182
+ const open = view.checks.filter((c) => !c.terminal).map((c) => c.name);
183
+ fail(
184
+ `timed out after ${timeoutSec}s — still not terminal: ` +
185
+ (open.join(", ") || "check set still settling"),
186
+ );
187
+ }
188
+ await new Promise((resolve) => setTimeout(resolve, intervalSec * 1000));
189
+ }
190
+ }