create-agent-rig 0.4.0 → 0.6.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 (118) hide show
  1. package/CHANGELOG.md +316 -11
  2. package/README.md +58 -19
  3. package/package.json +2 -1
  4. package/packages/cli/dist/commands/create.js +8 -3
  5. package/packages/cli/dist/commands/init.js +85 -34
  6. package/packages/cli/dist/commands/upgrade.js +112 -30
  7. package/packages/cli/dist/index.js +81 -19
  8. package/packages/cli/dist/lib/copy-tree.js +35 -6
  9. package/packages/cli/dist/lib/init-settings.js +12 -0
  10. package/packages/cli/dist/lib/install-set.js +6 -8
  11. package/packages/cli/dist/lib/manifest.js +31 -9
  12. package/packages/cli/dist/lib/safe-path.js +30 -0
  13. package/scripts/prepare.mjs +1 -1
  14. package/templates/agent-os/init/AGENTS.md +199 -0
  15. package/templates/agent-os/init/CLAUDE.md +69 -9
  16. package/templates/agent-os/stack/aws-cdk/.agents/skills/post-deploy-verify/SKILL.md +105 -0
  17. package/templates/agent-os/stack/aws-cdk/.agents/skills/ro-debug/SKILL.md +117 -0
  18. package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +41 -3
  19. package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +1 -1
  20. package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +41 -0
  21. package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +4 -0
  22. package/templates/agent-os/stack/node-ts/.claude/rules/node-ts.md +29 -0
  23. package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +236 -0
  24. package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +958 -0
  25. package/templates/agent-os/universal/.agents/skills/new-invariant/SKILL.md +102 -0
  26. package/templates/agent-os/universal/.agents/skills/new-invariant/guard-invariant.example.mjs +78 -0
  27. package/templates/agent-os/universal/.agents/skills/new-invariant/guard-invariant.example.test.mjs +89 -0
  28. package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +396 -0
  29. package/templates/agent-os/universal/.agents/skills/worktree-task/SKILL.md +73 -0
  30. package/templates/agent-os/universal/.claude/agents/code-reviewer.md +40 -1
  31. package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +71 -1
  32. package/templates/agent-os/universal/.claude/agents/security-scanner.md +40 -0
  33. package/templates/agent-os/universal/.claude/hooks/gate-stop-dod.mjs +301 -26
  34. package/templates/agent-os/universal/.claude/hooks/guard-bash.mjs +43 -5
  35. package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +25 -11
  36. package/templates/agent-os/universal/.claude/hooks/guard-rulebook.mjs +127 -0
  37. package/templates/agent-os/universal/.claude/hooks/guard-secret-file.mjs +180 -0
  38. package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +32 -13
  39. package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +175 -7
  40. package/templates/agent-os/universal/.claude/hooks/lib/edit-input.mjs +503 -0
  41. package/templates/agent-os/universal/.claude/rules/autonomy.md +69 -1
  42. package/templates/agent-os/universal/.claude/rules/invariants.md +104 -19
  43. package/templates/agent-os/universal/.claude/rules/workflow.md +55 -5
  44. package/templates/agent-os/universal/.claude/scripts/decision-router.mjs +1260 -0
  45. package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +65 -2
  46. package/templates/agent-os/universal/.claude/scripts/doctor.mjs +351 -0
  47. package/templates/agent-os/universal/.claude/scripts/git-env.mjs +49 -0
  48. package/templates/agent-os/universal/.claude/scripts/lib/gate-coverage.mjs +306 -0
  49. package/templates/agent-os/universal/.claude/scripts/lib/revalidation-points.mjs +28 -0
  50. package/templates/agent-os/universal/.claude/scripts/lib/secrets.mjs +490 -0
  51. package/templates/agent-os/universal/.claude/scripts/lib/verdict.mjs +462 -0
  52. package/templates/agent-os/universal/.claude/scripts/preflight.mjs +33 -17
  53. package/templates/agent-os/universal/.claude/scripts/queue/as-of.mjs +51 -0
  54. package/templates/agent-os/universal/.claude/scripts/queue/checkout.mjs +149 -0
  55. package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +809 -28
  56. package/templates/agent-os/universal/.claude/scripts/queue/gate-rounds.mjs +160 -0
  57. package/templates/agent-os/universal/.claude/scripts/queue/github-issues.mjs +95 -16
  58. package/templates/agent-os/universal/.claude/scripts/queue/index.mjs +543 -15
  59. package/templates/agent-os/universal/.claude/scripts/queue/jira.mjs +457 -47
  60. package/templates/agent-os/universal/.claude/scripts/queue/plan-md.mjs +289 -25
  61. package/templates/agent-os/universal/.claude/scripts/queue/state.mjs +197 -0
  62. package/templates/agent-os/universal/.claude/scripts/revalidate.mjs +316 -0
  63. package/templates/agent-os/universal/.claude/scripts/revalidation-report.mjs +180 -0
  64. package/templates/agent-os/universal/.claude/scripts/run-journal.mjs +435 -0
  65. package/templates/agent-os/universal/.claude/scripts/run-state.mjs +539 -0
  66. package/templates/agent-os/universal/.claude/scripts/stop-flag.mjs +15 -8
  67. package/templates/agent-os/universal/.claude/scripts/unattended-flag.mjs +239 -0
  68. package/templates/agent-os/universal/.claude/scripts/verdict.mjs +198 -0
  69. package/templates/agent-os/universal/.claude/settings.json +11 -2
  70. package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +118 -7
  71. package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +667 -38
  72. package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +359 -26
  73. package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +4 -0
  74. package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +4 -0
  75. package/templates/agent-os/universal/.codex/agents/security-scanner.toml +4 -0
  76. package/templates/agent-os/universal/.codex/agents/test-writer.toml +4 -0
  77. package/templates/agent-os/universal/.codex/hooks.json +70 -0
  78. package/templates/agent-os/universal/AGENTS.md +166 -0
  79. package/templates/agent-os/universal/CLAUDE.md +49 -14
  80. package/templates/agent-os/universal/PLAN.md +7 -40
  81. package/templates/agent-os/universal/docs/decisions/closing-a-task.md +98 -0
  82. package/templates/agent-os/universal/docs/decisions/codex-adapter.md +108 -0
  83. package/templates/agent-os/universal/docs/decisions/fail-open-guards.md +43 -0
  84. package/templates/agent-os/universal/docs/decisions/gate-coverage.md +83 -0
  85. package/templates/agent-os/universal/docs/decisions/review-lanes.md +72 -0
  86. package/templates/agent-os/universal/docs/decisions/run-directory.md +44 -0
  87. package/templates/agent-os/universal/docs/decisions/spacing-rations-mechanisms.md +125 -0
  88. package/templates/agent-os/universal/docs/decisions/stop-conditions-in-a-file.md +46 -0
  89. package/templates/agent-os/universal/docs/decisions/two-empty-endings.md +74 -0
  90. package/templates/agent-os/universal/journal/README.md +101 -0
  91. package/templates/agent-os/universal/layers.json +45 -2
  92. package/templates/hash-history.json +310 -49
  93. package/templates/release-ledger.json +9 -0
  94. package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +34 -1
  95. package/templates/skeleton/aws-serverless/README.md +91 -9
  96. package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +9 -2
  97. package/templates/skeleton/aws-serverless/gitignore +37 -0
  98. package/templates/skeleton/aws-serverless/infra/bin/app.ts +73 -13
  99. package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +136 -4
  100. package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +14 -1
  101. package/templates/skeleton/aws-serverless/infra/test/allowed-origins.test.ts +301 -0
  102. package/templates/skeleton/aws-serverless/infra/test/app-composition.test.ts +137 -0
  103. package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +36 -20
  104. package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +15 -12
  105. package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +33 -6
  106. package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +18 -5
  107. package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +11 -0
  108. package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +120 -1
  109. package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +72 -1
  110. package/templates/skeleton/node-service/README.md +11 -1
  111. package/templates/skeleton/node-service/gitignore +34 -0
  112. package/templates/skeleton/node-service/packages/db/src/note-store.ts +47 -10
  113. package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +20 -0
  114. package/templates/skeleton/node-service/services/api/src/main.ts +2 -9
  115. package/templates/skeleton/node-service/services/api/src/server.ts +93 -10
  116. package/templates/skeleton/node-service/services/api/src/static-dir.ts +20 -0
  117. package/templates/skeleton/node-service/services/api/test/server.test.ts +98 -13
  118. package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +28 -0
@@ -10,8 +10,144 @@
10
10
  // The injected content is deliberately STATELESS — rules, never facts about
11
11
  // the moment (mid-session injections are replayed on resume, so timestamps
12
12
  // or SHAs here would lie). And it is only the load-bearing part, not the
13
- // whole rulebook: CLAUDE.md is already loaded by the tool itself.
14
- import { readFileSync } from 'node:fs';
13
+ // whole rulebook.
14
+ //
15
+ // Which part is load-bearing is not this hook's judgment to make: the rule
16
+ // file marks what it does not need injected, and everything else goes. That
17
+ // division of labour is the whole design, and it was arrived at the expensive
18
+ // way. An earlier version of this file selected `## ` sections from a kept
19
+ // list — and four review rounds each found a different way for that selection
20
+ // to return a silently truncated excerpt: a heading inside a code fence, a
21
+ // heading inside a skipped region, a section whose heading and body fell on
22
+ // opposite sides of a marker, a heading inside an HTML block. Each fix closed
23
+ // one spelling and the next round found another, because a parser that infers
24
+ // structure has no bottom. This one does not parse structure at all.
25
+ //
26
+ // ⚠ The saving rests on an assumption this repository cannot enforce: that the
27
+ // tool already loads `.claude/rules/*.md` as project instructions, so injecting
28
+ // the whole file pays for it twice. That is harness behaviour, observable but
29
+ // not pinned here. Where it does not hold, this is a plain subtraction — which
30
+ // is why every ambiguity resolves toward injecting more.
31
+ import { readFileSync, realpathSync } from 'node:fs';
32
+ import { fileURLToPath } from 'node:url';
33
+
34
+ // Regions the rule file marks as not worth injecting. The marker is explicit
35
+ // and lives in the rule file itself, where the person editing it can see it —
36
+ // an earlier version keyed on heading level instead, which made "what every
37
+ // session is governed by" a silent consequence of a formatting choice.
38
+ const SKIP_OPEN = '<!-- inject:skip -->';
39
+ const SKIP_CLOSE = '<!-- /inject:skip -->';
40
+
41
+ /**
42
+ * A fence opener, as CommonMark defines one: three or more backticks or
43
+ * tildes. The closer has to be the same character and no shorter, which is why
44
+ * this returns the run rather than a boolean — a three-backtick line inside a
45
+ * four-backtick block is content, not the end of the block.
46
+ */
47
+ function fenceRun(line) {
48
+ const trimmed = line.trimStart();
49
+ const char = trimmed[0];
50
+ if (char !== '`' && char !== '~') return null;
51
+ let length = 0;
52
+ while (trimmed[length] === char) length += 1;
53
+ if (length < 3) return null;
54
+ // `bare` is what separates a closer from an opener: an opener may carry an
55
+ // info string (```sh), a closer may not.
56
+ return { char, length, bare: trimmed.slice(length).trim() === '' };
57
+ }
58
+
59
+ /**
60
+ * The whole file, minus every region the file itself marks with
61
+ * `<!-- inject:skip -->` … `<!-- /inject:skip -->`. That is the entire
62
+ * operation: nothing here reads a heading, so no arrangement of headings —
63
+ * indented, inside an HTML block, split across a marker — can change what
64
+ * survives. What is omitted is a decision made in the rule file, by whoever
65
+ * writes the rule, and visible on the line above it.
66
+ *
67
+ * Fenced code is data, not structure, so a marker inside a fence is content.
68
+ * Both fence characters, with the CommonMark closing rule (same character, at
69
+ * least as long, nothing but whitespace after it) — a rules file quotes shell
70
+ * and markdown at each other, and an opener may carry an info string where a
71
+ * closer may not.
72
+ *
73
+ * It returns the input UNCHANGED whenever the markup is malformed: a skip
74
+ * region left open, one closed without being opened, one nested inside
75
+ * another, or a fence left open. Partial output is the dangerous answer,
76
+ * because a governance section can go missing with nothing to notice it; a run
77
+ * that gets the whole file has only paid twice.
78
+ *
79
+ * The limits, so nobody relies on cover that is not here. A marker is
80
+ * recognised only as the first non-whitespace text on its own line — so one
81
+ * inside a blockquote is not a marker, and its text reaches the context. And
82
+ * fence-awareness is the only structure it knows: a BALANCED marker pair
83
+ * written inside an indented code block or an HTML comment is obeyed, so a
84
+ * document that demonstrates the markers loses the lines between them. Show
85
+ * them inside a fence.
86
+ */
87
+ export function excerptAutonomy(markdown) {
88
+ const lines = markdown.split('\n');
89
+ const kept = [];
90
+ let skipping = false;
91
+ let fence = null;
92
+ let malformed = false;
93
+
94
+ for (const line of lines) {
95
+ const run = fenceRun(line);
96
+ if (fence) {
97
+ if (run && run.char === fence.char && run.length >= fence.length && run.bare) fence = null;
98
+ } else if (run) {
99
+ fence = run;
100
+ } else {
101
+ const trimmed = line.trim();
102
+ // Matched by prefix: a marker with something after it is still a marker,
103
+ // and the whole line goes. Requiring the line to be exactly the marker
104
+ // would let `<!-- inject:skip --> note` open nothing and then print
105
+ // itself into the context.
106
+ if (trimmed.startsWith(SKIP_OPEN)) {
107
+ // A second open inside a region is as much a mistake as a stray close,
108
+ // and the two are the same signal: the markers do not pair up.
109
+ malformed ||= skipping;
110
+ skipping = true;
111
+ continue;
112
+ }
113
+ if (trimmed.startsWith(SKIP_CLOSE)) {
114
+ malformed ||= !skipping;
115
+ skipping = false;
116
+ continue;
117
+ }
118
+ }
119
+ if (!skipping) kept.push(line);
120
+ }
121
+
122
+ if (fence || skipping || malformed) return markdown;
123
+ const excerpt = kept.join('\n').trim();
124
+ // Nothing left is the largest possible version of "partial output", and the
125
+ // marker pairing cannot see it: a balanced pair around the whole file is
126
+ // well-formed. A zero, not a threshold — this is deliberately not the byte
127
+ // ratio an earlier round used and deleted.
128
+ return excerpt === '' ? markdown : excerpt;
129
+ }
130
+
131
+ /**
132
+ * Whether this file is being run as a script rather than imported.
133
+ *
134
+ * The realpath on both sides is the point: ESM resolves `import.meta.url`
135
+ * through symlinks while `process.argv[1]` keeps the path as typed, so a
136
+ * project under a symlinked directory — a macOS temp dir, a symlinked home, a
137
+ * checkout behind a link — fails a naive equality check. The hook would then
138
+ * print nothing and exit 0, which reads exactly like a healthy session.
139
+ */
140
+ function invokedDirectly() {
141
+ if (!process.argv[1]) return false;
142
+ const real = (p) => {
143
+ try {
144
+ return realpathSync(p);
145
+ } catch {
146
+ return p;
147
+ }
148
+ };
149
+ return real(fileURLToPath(import.meta.url)) === real(process.argv[1]);
150
+ }
15
151
 
16
152
  function main() {
17
153
  let input;
@@ -22,15 +158,47 @@ function main() {
22
158
  }
23
159
  if (input.hook_event_name !== 'SessionStart') return 0;
24
160
 
161
+ let rules;
25
162
  try {
26
- const rules = readFileSync(new URL('../rules/autonomy.md', import.meta.url), 'utf8');
27
- process.stdout.write(
28
- `[agent-os] Autonomy rules refresh — in force regardless of compaction:\n\n${rules}\n`,
29
- );
163
+ rules = readFileSync(new URL('../rules/autonomy.md', import.meta.url), 'utf8');
30
164
  } catch {
31
165
  // no rules file — nothing to inject, never an error
166
+ return 0;
167
+ }
168
+
169
+ // The cut is the only thing allowed to fail here, and its failure resolves
170
+ // the way every other ambiguity in this file does: inject more. Leaving it
171
+ // inside the read's catch meant a throw in the excerpter printed NOTHING and
172
+ // exited 0 — the rules gone, and the session looking healthy.
173
+ let body;
174
+ try {
175
+ body = excerptAutonomy(rules);
176
+ } catch {
177
+ body = rules;
32
178
  }
179
+
180
+ // The banner reports what happened, so it cannot be written once and assumed:
181
+ // on the fallback path nothing was removed, and telling a session to go read
182
+ // four sections it is already holding is the same kind of false report the
183
+ // cut itself is built to avoid.
184
+ // Compared trimmed, because the two paths differ by a trailing newline that
185
+ // is not a removal: the excerpt is trimmed and the fallback is not. A rules
186
+ // file that marks nothing would otherwise be announced as an excerpt — which
187
+ // is the case a project re-scoping its own rulebook lands in, every session.
188
+ const notice =
189
+ body.trim() === rules.trim()
190
+ ? 'This is `.claude/rules/autonomy.md` in full.\n\n'
191
+ : 'This is `.claude/rules/autonomy.md` with the sections it marks as ' +
192
+ 'reference removed — read the file itself for those: how the Tier-2 ' +
193
+ 'gate is swept from outside, how external work is reconciled, ' +
194
+ 'post-deploy verification, and the escalation format.\n\n';
195
+
196
+ process.stdout.write(
197
+ `[agent-os] Autonomy rules refresh — in force regardless of compaction.\n${notice}${body}\n`,
198
+ );
33
199
  return 0;
34
200
  }
35
201
 
36
- process.exit(main());
202
+ if (invokedDirectly()) {
203
+ process.exit(main());
204
+ }