@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,139 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { isPrintableSingleLine } from "../sdk/text.mjs";
5
+ import { isLedgerStringArray, isPlainLedgerRecord, MANIFEST_HASH_PATTERN } from "./state-validation.mjs";
6
+
7
+ export const WORKER_METADATA_REL = ".stdd/worker.json";
8
+ export const WORKER_METADATA_SCHEMA = 2;
9
+ export const WORKER_METADATA_READABLE_SCHEMAS = new Set([1, WORKER_METADATA_SCHEMA]);
10
+ export const WORKER_EVIDENCE_EVENTS = new Set(["red", "verify", "note"]);
11
+ export const WORKER_LOCAL_COMMANDS = new Set([
12
+ "red",
13
+ "verify",
14
+ "note",
15
+ "status",
16
+ "scope",
17
+ "doctor",
18
+ "version",
19
+ "--version",
20
+ ]);
21
+
22
+ const WORKER_ID_RANDOM_BYTES = 12;
23
+ export const WORKER_ID_PATTERN = /^worker-[0-9a-f]{24}$/u;
24
+
25
+ export function createWorkerId() {
26
+ return `worker-${randomBytes(WORKER_ID_RANDOM_BYTES).toString("hex")}`;
27
+ }
28
+
29
+ export function findWorkerRoot(start) {
30
+ let candidate = path.resolve(start);
31
+ while (true) {
32
+ if (fs.existsSync(path.join(candidate, WORKER_METADATA_REL))) return candidate;
33
+ const parent = path.dirname(candidate);
34
+ if (parent === candidate) return null;
35
+ candidate = parent;
36
+ }
37
+ }
38
+
39
+ function exactKeys(value, keys) {
40
+ if (!isPlainLedgerRecord(value)) return false;
41
+ const actual = Object.keys(value).sort();
42
+ const expected = [...keys].sort();
43
+ return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
44
+ }
45
+
46
+ function validPortableIdentity(identity, kind) {
47
+ return (
48
+ exactKeys(identity, ["version", "platform", "volume", "fileId", "kind"]) &&
49
+ identity.version === 2 &&
50
+ ["linux", "darwin", "win32"].includes(identity.platform) &&
51
+ typeof identity.volume === "string" &&
52
+ typeof identity.fileId === "string" &&
53
+ identity.kind === kind
54
+ );
55
+ }
56
+
57
+ function validateWorkerFileState(state, schema) {
58
+ const portableValid =
59
+ schema === 1 ||
60
+ (exactKeys(state?.portable, ["source", "sandbox"]) &&
61
+ validPortableIdentity(state.portable.source, state.type) &&
62
+ validPortableIdentity(state.portable.sandbox, state.type));
63
+ return (
64
+ state === null ||
65
+ (isPlainLedgerRecord(state) &&
66
+ portableValid &&
67
+ ((state.type === "file" &&
68
+ exactKeys(
69
+ state,
70
+ schema === 1 ? ["type", "hash", "mode"] : ["type", "hash", "mode", "portable"],
71
+ ) &&
72
+ MANIFEST_HASH_PATTERN.test(state.hash) &&
73
+ (schema === 1
74
+ ? Number.isInteger(state.mode) && state.mode >= 0 && state.mode <= 0o777
75
+ : [0o600, 0o644, 0o755].includes(state.mode))) ||
76
+ (state.type === "symlink" &&
77
+ exactKeys(
78
+ state,
79
+ schema === 1
80
+ ? ["type", "target", "hash"]
81
+ : ["type", "target", "targetBase64", "hash", "portable"],
82
+ ) &&
83
+ typeof state.target === "string" &&
84
+ (schema === 1 ||
85
+ (typeof state.targetBase64 === "string" &&
86
+ Buffer.from(state.targetBase64, "base64").toString("base64") === state.targetBase64)) &&
87
+ MANIFEST_HASH_PATTERN.test(state.hash))))
88
+ );
89
+ }
90
+
91
+ export function parseWorkerMetadata(metadataBytes, root, metadataPath) {
92
+ let parsed;
93
+ try {
94
+ parsed = JSON.parse(metadataBytes);
95
+ } catch (err) {
96
+ throw new Error(`invalid managed worker metadata: ${err.message}`);
97
+ }
98
+ if (
99
+ !WORKER_METADATA_READABLE_SCHEMAS.has(parsed?.schema) ||
100
+ !WORKER_ID_PATTERN.test(parsed.workerId ?? "") ||
101
+ !isPlainLedgerRecord(parsed.source) ||
102
+ !isPrintableSingleLine(parsed.source.root) ||
103
+ !isPrintableSingleLine(parsed.source.branch) ||
104
+ !isPrintableSingleLine(parsed.source.taskId) ||
105
+ !isPrintableSingleLine(parsed.source.taskName) ||
106
+ !isPrintableSingleLine(parsed.source.head) ||
107
+ !isPlainLedgerRecord(parsed.scope) ||
108
+ !isLedgerStringArray(parsed.scope.frozenPaths) ||
109
+ !isLedgerStringArray(parsed.scope.allowedPaths) ||
110
+ parsed.scope.frozenPaths.length + parsed.scope.allowedPaths.length === 0 ||
111
+ !isPlainLedgerRecord(parsed.baseline) ||
112
+ !isPlainLedgerRecord(parsed.baseline.files) ||
113
+ !Object.values(parsed.baseline.files).every((state) => validateWorkerFileState(state, parsed.schema))
114
+ ) {
115
+ throw new Error("invalid managed worker metadata schema");
116
+ }
117
+ parsed.baseline.files = Object.assign(Object.create(null), parsed.baseline.files);
118
+ return { ...parsed, root, metadataPath, metadataBytes };
119
+ }
120
+
121
+ export function readWorkerMetadata(cwd, { required = false } = {}) {
122
+ const root = findWorkerRoot(cwd);
123
+ if (!root) {
124
+ if (required) throw new Error("not a managed gitless worker sandbox");
125
+ return null;
126
+ }
127
+ const metadataPath = path.join(root, WORKER_METADATA_REL);
128
+ let metadataBytes;
129
+ try {
130
+ const observed = fs.lstatSync(metadataPath);
131
+ if (observed.isSymbolicLink() || !observed.isFile() || observed.nlink !== 1) {
132
+ throw new Error("metadata must be a single-linked regular file");
133
+ }
134
+ metadataBytes = fs.readFileSync(metadataPath, "utf8");
135
+ } catch (err) {
136
+ throw new Error(`invalid managed worker metadata: ${err.message}`);
137
+ }
138
+ return parseWorkerMetadata(metadataBytes, root, metadataPath);
139
+ }