engsys 1.0.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 (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -0
  3. package/core/agents/aaron.md +152 -0
  4. package/core/agents/bert.md +115 -0
  5. package/core/agents/isabelle.md +136 -0
  6. package/core/agents/jody.md +150 -0
  7. package/core/agents/leith.md +111 -0
  8. package/core/agents/marcelo.md +282 -0
  9. package/core/agents/melvin.md +101 -0
  10. package/core/agents/nyx.md +152 -0
  11. package/core/agents/otto.md +168 -0
  12. package/core/agents/patricia.md +283 -0
  13. package/core/commands/design-audit-local.md +155 -0
  14. package/core/commands/design-audit.md +235 -0
  15. package/core/commands/design-critique.md +96 -0
  16. package/core/commands/file-issue.md +22 -0
  17. package/core/commands/generate-project.md +45 -0
  18. package/core/commands/implement-issue.md +37 -0
  19. package/core/commands/implement-project.md +40 -0
  20. package/core/commands/naturalize.md +61 -0
  21. package/core/commands/pre-push.md +29 -0
  22. package/core/commands/prep-review-collect.md +130 -0
  23. package/core/commands/prep-review-finalize.md +121 -0
  24. package/core/commands/prep-review-publish.md +113 -0
  25. package/core/commands/prep-review.md +65 -0
  26. package/core/commands/project-closeout.md +25 -0
  27. package/core/skills/agentic-eval/SKILL.md +195 -0
  28. package/core/skills/chrome-devtools/SKILL.md +97 -0
  29. package/core/skills/code-review/SKILL.md +26 -0
  30. package/core/skills/gh-cli/SKILL.md +2202 -0
  31. package/core/skills/git-commit/SKILL.md +124 -0
  32. package/core/skills/git-workflow-agents/SKILL.md +462 -0
  33. package/core/skills/git-workflow-agents/reference.md +220 -0
  34. package/core/skills/github-actions/SKILL.md +190 -0
  35. package/core/skills/github-issues/SKILL.md +154 -0
  36. package/core/skills/llm-structured-outputs/SKILL.md +323 -0
  37. package/core/skills/llm-structured-outputs/references/provider-details.md +392 -0
  38. package/core/skills/pre-push/SKILL.md +115 -0
  39. package/core/skills/refactor/SKILL.md +645 -0
  40. package/core/skills/web-design-reviewer/SKILL.md +371 -0
  41. package/core/skills/webapp-testing/SKILL.md +127 -0
  42. package/core/skills/webapp-testing/test-helper.js +56 -0
  43. package/core/templates/CLAUDE.md.tmpl +98 -0
  44. package/core/templates/adr-template.md +67 -0
  45. package/core/templates/gh-issue-templates/bug.md +39 -0
  46. package/core/templates/gh-issue-templates/content.md +42 -0
  47. package/core/templates/gh-issue-templates/enhancement.md +36 -0
  48. package/core/templates/gh-issue-templates/feature.md +39 -0
  49. package/core/templates/gh-issue-templates/infrastructure.md +41 -0
  50. package/core/templates/post-edit-reminders.sh.tmpl +19 -0
  51. package/core/templates/settings.json.tmpl +90 -0
  52. package/core/templates/settings.local.json.tmpl +3 -0
  53. package/core/workflows/agent-implementation-workflow.md +346 -0
  54. package/core/workflows/generate-project.md +258 -0
  55. package/core/workflows/implement-project-workflow.md +190 -0
  56. package/core/workflows/issue-tracking.md +89 -0
  57. package/core/workflows/project-closeout-ceremony.md +77 -0
  58. package/core/workflows/review-workflow.md +266 -0
  59. package/engsys.config.example.yaml +46 -0
  60. package/install +202 -0
  61. package/lessons-library/README.md +80 -0
  62. package/lessons-library/async-callbacks-verify-liveness.md +15 -0
  63. package/lessons-library/change-isnt-done-until-every-surface-updated.md +15 -0
  64. package/lessons-library/claim-then-act-for-irreversible-ops.md +16 -0
  65. package/lessons-library/co-commit-entangled-work.md +15 -0
  66. package/lessons-library/dependabot-triage-playbook.md +17 -0
  67. package/lessons-library/deploy-by-digest-and-verify-the-running-revision.md +15 -0
  68. package/lessons-library/enforce-your-guarantee-at-your-boundary.md +16 -0
  69. package/lessons-library/gate-changes-on-measurement-not-vibes.md +15 -0
  70. package/lessons-library/iac-first-no-console-changes.md +15 -0
  71. package/lessons-library/independent-objective-review-gate.md +15 -0
  72. package/lessons-library/keep-an-immutable-source-of-truth.md +15 -0
  73. package/lessons-library/long-agent-runs-checkpoint-not-poll.md +15 -0
  74. package/lessons-library/model-identity-with-stable-ids-and-provenance.md +15 -0
  75. package/lessons-library/operator-choices-are-first-class.md +15 -0
  76. package/lessons-library/prefer-tool-enforced-structured-output.md +15 -0
  77. package/lessons-library/prove-causation-before-acting.md +15 -0
  78. package/lessons-library/re-read-state-before-acting.md +14 -0
  79. package/lessons-library/read-layer-tolerates-unbackfilled-rows.md +15 -0
  80. package/lessons-library/shell-safety-pipefail-and-validate-before-teardown.md +14 -0
  81. package/lessons-library/shift-correctness-left-and-distrust-false-greens.md +15 -0
  82. package/lessons-library/stray-control-bytes-hide-changes.md +14 -0
  83. package/lessons-library/tests-can-assert-the-bug.md +15 -0
  84. package/lessons-library/verify-ground-truth-not-reports.md +15 -0
  85. package/lessons-library/worktrees-need-bootstrap-from-origin-main.md +15 -0
  86. package/lib/commands.js +356 -0
  87. package/lib/generate-team-avatars.mjs +251 -0
  88. package/lib/manifest.js +155 -0
  89. package/lib/render.js +135 -0
  90. package/lib/selftest.js +90 -0
  91. package/lib/util.js +89 -0
  92. package/lib/yaml.js +156 -0
  93. package/optional-agents/gary.md +86 -0
  94. package/optional-agents/jos.md +136 -0
  95. package/optional-agents/sandy.md +101 -0
  96. package/optional-agents/steve.md +161 -0
  97. package/package.json +43 -0
  98. package/stacks/cloud/aws/claude.fragment.md +17 -0
  99. package/stacks/cloud/aws/settings.fragment.json +39 -0
  100. package/stacks/cloud/aws/skills/aws-deployment-preflight/SKILL.md +165 -0
  101. package/stacks/cloud/aws/skills/cloud-architecture-aws/SKILL.md +265 -0
  102. package/stacks/cloud/azure/claude.fragment.md +17 -0
  103. package/stacks/cloud/azure/settings.fragment.json +45 -0
  104. package/stacks/cloud/azure/skills/azure-deployment-preflight/SKILL.md +175 -0
  105. package/stacks/cloud/azure/skills/cloud-architecture-azure/SKILL.md +211 -0
  106. package/stacks/cloud/cloudflare/claude.fragment.md +21 -0
  107. package/stacks/cloud/cloudflare/settings.fragment.json +31 -0
  108. package/stacks/cloud/cloudflare/skills/cloud-architecture-cloudflare/SKILL.md +294 -0
  109. package/stacks/cloud/cloudflare/skills/cloudflare-deployment-preflight/SKILL.md +175 -0
  110. package/stacks/cloud/gcp/claude.fragment.md +17 -0
  111. package/stacks/cloud/gcp/settings.fragment.json +40 -0
  112. package/stacks/cloud/gcp/skills/cloud-architecture-gcp/SKILL.md +208 -0
  113. package/stacks/cloud/gcp/skills/gcp-deployment-preflight/SKILL.md +137 -0
  114. package/stacks/db/mongo/skills/mongo-conventions/SKILL.md +96 -0
  115. package/stacks/db/prisma/claude.fragment.md +49 -0
  116. package/stacks/db/prisma/skills/docker-database-package-copy/SKILL.md +44 -0
  117. package/stacks/db/prisma/skills/prisma-conventions/SKILL.md +37 -0
  118. package/stacks/domain/mobile-growth/skills/apple-ads/SKILL.md +184 -0
  119. package/stacks/domain/mobile-growth/skills/apple-ads/references/benchmark-notes.md +47 -0
  120. package/stacks/domain/mobile-growth/skills/apple-ads/references/official-links.md +53 -0
  121. package/stacks/domain/mobile-growth/skills/google-play-growth/SKILL.md +197 -0
  122. package/stacks/domain/mobile-growth/skills/google-play-growth/references/benchmark-notes.md +47 -0
  123. package/stacks/domain/mobile-growth/skills/google-play-growth/references/official-links.md +45 -0
  124. package/stacks/iac/bicep/claude.fragment.md +14 -0
  125. package/stacks/iac/bicep/settings.fragment.json +20 -0
  126. package/stacks/iac/bicep/skills/iac-bicep/SKILL.md +113 -0
  127. package/stacks/iac/cdk/claude.fragment.md +14 -0
  128. package/stacks/iac/cdk/settings.fragment.json +23 -0
  129. package/stacks/iac/cdk/skills/iac-cdk/SKILL.md +104 -0
  130. package/stacks/iac/terraform/claude.fragment.md +13 -0
  131. package/stacks/iac/terraform/settings.fragment.json +25 -0
  132. package/stacks/iac/terraform/skills/iac-terraform/SKILL.md +93 -0
  133. package/stacks/iac/terraform/skills/terraform-conventions/SKILL.md +87 -0
  134. package/stacks/lang/kotlin/skills/android-testing/SKILL.md +263 -0
  135. package/stacks/lang/kotlin/skills/jetpack-compose/SKILL.md +264 -0
  136. package/stacks/lang/kotlin/skills/kotlin-coroutines/SKILL.md +329 -0
  137. package/stacks/lang/python/skills/python-conventions/SKILL.md +61 -0
  138. package/stacks/lang/shell/skills/shell-scripting/SKILL.md +110 -0
  139. package/stacks/lang/swift/skills/swift-concurrency/SKILL.md +423 -0
  140. package/stacks/lang/swift/skills/swift-concurrency/references/approachable-concurrency.md +80 -0
  141. package/stacks/lang/swift/skills/swift-concurrency/references/concurrency-patterns.md +233 -0
  142. package/stacks/lang/swift/skills/swift-concurrency/references/swiftui-concurrency.md +187 -0
  143. package/stacks/lang/swift/skills/swift-concurrency/references/synchronization-primitives.md +341 -0
  144. package/stacks/lang/swift/skills/swift-testing/SKILL.md +497 -0
  145. package/stacks/lang/swift/skills/swift-testing/references/testing-advanced.md +106 -0
  146. package/stacks/lang/swift/skills/swift-testing/references/testing-patterns.md +504 -0
  147. package/stacks/lang/swift/skills/swiftdata/SKILL.md +334 -0
  148. package/stacks/lang/swift/skills/swiftdata/references/core-data-coexistence.md +504 -0
  149. package/stacks/lang/swift/skills/swiftdata/references/swiftdata-advanced.md +975 -0
  150. package/stacks/lang/swift/skills/swiftdata/references/swiftdata-queries.md +675 -0
  151. package/stacks/lang/swift/skills/swiftui-patterns/SKILL.md +371 -0
  152. package/stacks/lang/swift/skills/swiftui-patterns/references/architecture-patterns.md +486 -0
  153. package/stacks/lang/swift/skills/swiftui-patterns/references/deprecated-migration.md +1097 -0
  154. package/stacks/lang/swift/skills/swiftui-patterns/references/design-polish.md +780 -0
  155. package/stacks/lang/swift/skills/swiftui-patterns/references/platform-and-sharing.md +696 -0
  156. package/stacks/lang/typescript/skills/typescript-conventions/SKILL.md +91 -0
  157. package/stacks/platform/android/claude.fragment.md +40 -0
  158. package/stacks/platform/android/hooks/pre-push-gradle.sh +70 -0
  159. package/stacks/platform/android/settings.fragment.json +13 -0
  160. package/stacks/platform/android/skills/android-build-conventions/SKILL.md +247 -0
  161. package/stacks/platform/ios/claude.fragment.md +24 -0
  162. package/stacks/platform/ios/hooks/pre-push-xcodebuild.sh +82 -0
  163. package/stacks/platform/ios/settings.fragment.json +21 -0
  164. package/stacks/platform/ios/skills/xcodebuildmcp-simulator-logs/SKILL.md +76 -0
  165. package/stacks/platform/web/skills/frontend-testing/SKILL.md +246 -0
  166. package/stacks/platform/web/skills/react-conventions/SKILL.md +261 -0
  167. package/stacks/platform/web/skills/web-platform-conventions/SKILL.md +55 -0
  168. package/stacks/tooling/issue-tracker-github/claude.fragment.md +10 -0
  169. package/stacks/tooling/issue-tracker-github/settings.fragment.json +24 -0
  170. package/stacks/tooling/issue-tracker-github/skills/issue-tracker-github/SKILL.md +278 -0
  171. package/stacks/tooling/issue-tracker-linear/claude.fragment.md +17 -0
  172. package/stacks/tooling/issue-tracker-linear/settings.fragment.json +9 -0
  173. package/stacks/tooling/issue-tracker-linear/skills/issue-tracker-linear/SKILL.md +183 -0
@@ -0,0 +1,46 @@
1
+ # engsys project config — copy to your project root as engsys.config.yaml,
2
+ # edit, then run: /path/to/engsys/install install --into .
3
+ #
4
+ # This file is the single source of truth for what the installer materializes.
5
+ # Block lists must be indented under their key (see hook_patterns below).
6
+
7
+ project:
8
+ name: Acme Widgets
9
+ description: One-line description rendered into CLAUDE.md.
10
+
11
+ # Every stack dimension accepts a single value OR a list — mixed stacks compose
12
+ # (each selected pack contributes its skills, CLAUDE.md fragment, and permissions).
13
+ stack:
14
+ cloud: aws # aws | azure | gcp | cloudflare | none — or a list: [azure, cloudflare]
15
+ iac: terraform # terraform | bicep | cdk | none — or a list
16
+ lang: [typescript] # typescript | python | swift | kotlin | shell
17
+ platform: [web] # web | ios | android — e.g. [web, ios, android]
18
+ db: none # prisma | mongo | none — or a list: [prisma, mongo]
19
+ # domain: [mobile-growth] # optional extra packs under stacks/domain/
20
+
21
+ # Issue tracker (PRs / CI always stay on GitHub — this is a separate axis).
22
+ issue_tracker: github # github | linear | jira
23
+
24
+ agents:
25
+ core: all # 'all' or an explicit list, e.g. [melvin, isabelle, bert]
26
+ extra: [sandy, gary] # opt-in personas from optional-agents/ (sandy, gary [mobile], jos, steve)
27
+
28
+ commands: all # 'all' or an explicit list
29
+
30
+ lessons:
31
+ seed: true # seed the universal lessons-library into the project
32
+ into: docs/agent-lessons/library # where seeded lessons land (project-local lessons stay at docs/agent-lessons/)
33
+
34
+ naturalize:
35
+ model_strategy: "Opus for orchestration and judgement; Sonnet for execution."
36
+ hook_patterns:
37
+ - glob: "*/schema.prisma"
38
+ reminder: "Schema changed — regenerate the client and rebuild dependents before pushing."
39
+ - glob: "docs/vision-and-spec.md"
40
+ reminder: "Bump the version/date header; keep docs cross-refs in sync."
41
+ invariants: [] # project hard rules appended to CLAUDE.md project facts
42
+ # project_facts: | # optional; otherwise the installer leaves a TODO marker
43
+ # Multi-line project facts can go here.
44
+
45
+ engsys:
46
+ version: main # tag / branch / commit the project was installed from
package/install ADDED
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // engsys installer — deterministic plumbing for the Claude Code engineering system.
5
+ // Usage:
6
+ // ./install install --into <path> [--config <file>] [--dry-run] [--force]
7
+ // ./install update --into <path> [--config <file>] [--dry-run]
8
+ // ./install verify --into <path>
9
+ //
10
+ // The engsys source root is the directory this script lives in.
11
+
12
+ const path = require('path');
13
+ const fs = require('fs');
14
+ const { runInstall, runUninstall, runVerify, ENGSYS_VERSION } = require('./lib/commands');
15
+ const { exists } = require('./lib/util');
16
+
17
+ const engsysRoot = __dirname;
18
+
19
+ function parseArgs(argv) {
20
+ const args = { _: [] };
21
+ for (let i = 0; i < argv.length; i++) {
22
+ const a = argv[i];
23
+ if (a === '--dry-run') args.dryRun = true;
24
+ else if (a === '--force') args.force = true;
25
+ else if (a === '--into') args.into = argv[++i];
26
+ else if (a === '--config') args.config = argv[++i];
27
+ else if (a === '--help' || a === '-h') args.help = true;
28
+ else args._.push(a);
29
+ }
30
+ return args;
31
+ }
32
+
33
+ const USAGE = `engsys ${ENGSYS_VERSION}
34
+
35
+ Bring the engineering system into a project, faithfully — adopting whatever is
36
+ already there (existing CLAUDE.md, settings, agents, or Copilot/Cursor config).
37
+
38
+ Commands:
39
+ init [--into <path>] [--force]
40
+ Scaffold engsys.config.yaml in <path> (default: current dir) from the
41
+ bundled example. Handy after \`npm install -g engsys\`, where you don't
42
+ have the repo's example file to copy. Edit it, then run \`install\`.
43
+ install --into <path> [--config <file>] [--dry-run] [--force]
44
+ First-time materialization. Adopts existing infra: backs up a foreign
45
+ CLAUDE.md (folding it into project facts), merges settings/.mcp.json,
46
+ preserves the project's own agents, and imports Copilot/Cursor config.
47
+ Re-running over an existing install behaves as update.
48
+ update --into <path> [--config <file>] [--dry-run]
49
+ Re-render from current engsys + config. Preserves CLAUDE.md project facts
50
+ and hand-added permissions; prunes files orphaned by deselected packs.
51
+ verify --into <path>
52
+ Check installed files against the lock (drift detection).
53
+ uninstall --into <path> [--dry-run]
54
+ Remove everything engsys added and restore the project's prior files (from
55
+ .claude/.engsys-backup/). The project's own agents/files are left intact.
56
+
57
+ --force overwrite generated files (CLAUDE.md/settings/.mcp.json) instead of merging.
58
+
59
+ Config: <into>/engsys.config.yaml (or .yml / .json). See engsys.config.example.yaml.`;
60
+
61
+ function list(label, items, max = 8) {
62
+ if (!items || !items.length) return [];
63
+ const shown = items.slice(0, max).map((i) => ` ${i}`);
64
+ if (items.length > max) shown.push(` … and ${items.length - max} more`);
65
+ return [` ${label}:`, ...shown];
66
+ }
67
+
68
+ function report(result, cmd) {
69
+ const { into, plan } = result;
70
+ const lines = [];
71
+
72
+ if (result.adopting) lines.push(' note: existing engsys install detected → adopting as `update`.');
73
+ if (result.versionFrom && result.versionFrom !== result.versionTo) {
74
+ lines.push(` engsys: ${result.versionFrom} → ${result.versionTo}`);
75
+ }
76
+
77
+ lines.push(...result.actions.map((a) => ' ' + a));
78
+ const c = result.changes;
79
+ lines.push(` files: ${result.managedCount} managed (${c.added} new, ${c.updated} updated, ${c.removed} removed), ${result.generated.length} generated`);
80
+
81
+ // Adoption surfaces (scenario 1 & 3) + prune (scenario 2).
82
+ if (result.preexistingCount) {
83
+ const p = result.preexisting;
84
+ const bits = [];
85
+ if (p.agents.length) bits.push(`${p.agents.length} agent(s)`);
86
+ if (p.commands.length) bits.push(`${p.commands.length} command(s)`);
87
+ if (p.skills.length) bits.push(`${p.skills.length} skill(s)`);
88
+ lines.push(` preserved (the project's own): ${bits.join(', ')} — left untouched.`);
89
+ for (const a of p.agents.slice(0, 8)) lines.push(` ${a}`);
90
+ }
91
+ if (result.snapshotted && result.snapshotted.length) {
92
+ lines.push(` rollback: ${result.snapshotted.length} pre-existing file(s) snapshotted → \`engsys uninstall\` restores your prior setup.`);
93
+ } else if (result.firstInstall) {
94
+ lines.push(' rollback: `engsys uninstall` removes engsys and restores your prior setup.');
95
+ }
96
+ lines.push(...list('pruned (orphaned)', result.pruned));
97
+ if (result.importedNow) {
98
+ lines.push(` imported AI config: ${result.foreign.length} file(s) → docs/imported-ai-config/`);
99
+ lines.push(...result.foreign.slice(0, 8).map((f) => ` ${f.tool}: ${f.rel}`));
100
+ }
101
+
102
+ // Naturalization checklist (model-side; never plumbing).
103
+ lines.push('');
104
+ lines.push('Next — naturalization (model-side, never plumbing):');
105
+ lines.push(' - Fill the CLAUDE.md PROJECT-FACTS region (or run /naturalize): stack, services, invariants, key paths.');
106
+ if (result.foldedClaude) {
107
+ lines.push(' - Your prior CLAUDE.md was folded into PROJECT-FACTS (original preserved in .claude/.engsys-backup/) — trim it.');
108
+ }
109
+ if (result.importedNow) {
110
+ lines.push(' - Fold docs/imported-ai-config/ (Copilot/Cursor rules + agent defs) into CLAUDE.md / .claude/agents via /naturalize, then delete it.');
111
+ }
112
+ if (result.preexisting && result.preexisting.agents.length) {
113
+ lines.push(" - Reconcile the project's own agents with the engsys roster (keep, merge, or rename on conflict).");
114
+ }
115
+ const placeholderHints = [];
116
+ for (const f of plan.claudeFragments) {
117
+ if (/<naturalize:|<proj>|<rg>|<env>|<suffix>/.test(f.text)) placeholderHints.push(f.pack);
118
+ }
119
+ if (Object.keys(plan.mcpServers).some((k) => /<naturalize:/.test(JSON.stringify(plan.mcpServers[k])))) {
120
+ placeholderHints.push('.mcp.json (MCP server env)');
121
+ }
122
+ if (placeholderHints.length) lines.push(` - Replace <naturalize: ...> placeholders in: ${placeholderHints.join(', ')}.`);
123
+ lines.push(` - Review .claude/settings.json permissions${Object.keys(plan.mcpServers).length ? ' and .mcp.json' : ''} before first use.`);
124
+ lines.push(` - \`engsys verify --into ${into}\` any time to confirm nothing drifted.`);
125
+
126
+ if (plan.warnings.length || result.warnings.length) {
127
+ lines.push('');
128
+ lines.push('Warnings:');
129
+ for (const w of [...plan.warnings, ...result.warnings]) lines.push(` ! ${w}`);
130
+ }
131
+ return lines.join('\n');
132
+ }
133
+
134
+ function main() {
135
+ const args = parseArgs(process.argv.slice(2));
136
+ const cmd = args._[0];
137
+
138
+ if (args.help || !cmd) { console.log(USAGE); process.exit(args.help ? 0 : 1); }
139
+
140
+ if (cmd === 'init') {
141
+ const into = path.resolve(args.into || '.');
142
+ if (!exists(into)) { console.error(`error: target does not exist: ${into}`); process.exit(1); }
143
+ const dest = path.join(into, 'engsys.config.yaml');
144
+ const example = path.join(engsysRoot, 'engsys.config.example.yaml');
145
+ if (!exists(example)) { console.error(`error: bundled example not found: ${example}`); process.exit(1); }
146
+ if (exists(dest) && !args.force) {
147
+ console.error(`error: ${path.relative(into, dest) || dest} already exists (use --force to overwrite).`);
148
+ process.exit(1);
149
+ }
150
+ if (args.dryRun) { console.log(`engsys init (dry run — nothing written) → ${dest}`); process.exit(0); }
151
+ fs.writeFileSync(dest, fs.readFileSync(example, 'utf8'));
152
+ console.log(`engsys init → wrote ${path.relative(process.cwd(), dest) || dest}`);
153
+ console.log(' Edit it (pick cloud / iac / lang / platform / db / agents), then:');
154
+ console.log(` engsys install --into ${args.into || '.'}`);
155
+ process.exit(0);
156
+ }
157
+
158
+ if (cmd === 'install' || cmd === 'update') {
159
+ if (!args.into) { console.error('error: --into <path> is required'); process.exit(1); }
160
+ const into = path.resolve(args.into);
161
+ if (!exists(into)) { console.error(`error: target does not exist: ${into}`); process.exit(1); }
162
+ const result = runInstall({ engsysRoot, into, config: args.config, dryRun: args.dryRun, force: args.force, mode: cmd });
163
+ const tag = args.dryRun ? ' (dry run — nothing written)' : '';
164
+ console.log(`engsys ${result.mode}${tag} → ${into}`);
165
+ console.log(`config: ${path.relative(into, result.configPath) || result.configPath}`);
166
+ console.log(report(result, cmd));
167
+ process.exit(0);
168
+ }
169
+
170
+ if (cmd === 'uninstall') {
171
+ if (!args.into) { console.error('error: --into <path> is required'); process.exit(1); }
172
+ const into = path.resolve(args.into);
173
+ const r = runUninstall({ into, dryRun: args.dryRun });
174
+ const tag = args.dryRun ? ' (dry run — nothing written)' : '';
175
+ console.log(`engsys uninstall${tag} → ${into}`);
176
+ console.log(` removed ${r.removed.length} engsys file(s), restored ${r.restored.length} original(s).`);
177
+ if (!r.hadManifest) console.log(' note: no rollback manifest found — removed engsys files but had no snapshot to restore.');
178
+ const n = r.preexisting.agents.length + r.preexisting.commands.length + r.preexisting.skills.length;
179
+ if (n) console.log(` left the project's own ${n} agent/command/skill file(s) intact.`);
180
+ process.exit(0);
181
+ }
182
+
183
+ if (cmd === 'verify') {
184
+ if (!args.into) { console.error('error: --into <path> is required'); process.exit(1); }
185
+ const into = path.resolve(args.into);
186
+ const r = runVerify({ into });
187
+ if (r.ok) {
188
+ console.log(`verify: OK — ${Object.keys(r.lock.managed).length} managed files match (engsys ${r.lock.engsysVersion}).`);
189
+ process.exit(0);
190
+ }
191
+ console.log(`verify: DRIFT detected (engsys ${r.lock.engsysVersion}).`);
192
+ for (const m of r.missing) console.log(` missing: ${m}`);
193
+ for (const m of r.modified) console.log(` modified: ${m}`);
194
+ process.exit(2);
195
+ }
196
+
197
+ console.error(`unknown command: ${cmd}\n`);
198
+ console.log(USAGE);
199
+ process.exit(1);
200
+ }
201
+
202
+ main();
@@ -0,0 +1,80 @@
1
+ # lessons-library
2
+
3
+ Curated, **generalized** lessons that recur across projects — the durable memory
4
+ of the engineering system.
5
+
6
+ Two tiers, kept distinct:
7
+
8
+ 1. **Project-local** lessons live in each project's `docs/agent-lessons/`, written
9
+ during `/project-closeout` by mining that project's local-review findings.
10
+ 2. **Generalized** lessons live here. When a lesson family recurs across projects
11
+ (e.g. "new E2E spec ⇒ register it in the CI matrix", dependabot triage), it's
12
+ rewritten stack-agnostically and promoted here.
13
+
14
+ ## Promotion
15
+
16
+ `/project-closeout` ends with: *if a mined lesson generalizes, open a PR against
17
+ `engsys/lessons-library/`*. That's the feedback loop that keeps engsys the source
18
+ of truth instead of a fork point.
19
+
20
+ ## Format
21
+
22
+ One lesson per file. Keep them LLM-optimized and trigger-first:
23
+
24
+ ```markdown
25
+ # <short title>
26
+
27
+ **Trigger:** the symptom that should make you recall this.
28
+ **Failure mode:** what goes wrong and why.
29
+ **Correct behavior:** the checklist / the fix.
30
+ **Check:** a quick diagnostic before/after.
31
+ **Seen in:** keep it generalized — e.g. "recurring across projects."
32
+ ```
33
+
34
+ > This library is published publicly (npm + GitHub). Generalize before promoting:
35
+ > **never** include private project/repo/company names, secrets, internal hosts,
36
+ > or proprietary specifics in a lesson. The pattern is what's portable, not the
37
+ > particulars.
38
+
39
+ ## Seeding
40
+
41
+ A future installer option may seed a project's `docs/agent-lessons/` with the
42
+ lessons relevant to its chosen stack. For now, promotion is manual via PR.
43
+
44
+ ## Index
45
+
46
+ ### Verification & review
47
+ - [verify-ground-truth-not-reports](verify-ground-truth-not-reports.md) — check real state, not the narration.
48
+ - [independent-objective-review-gate](independent-objective-review-gate.md) — fresh reviewer re-runs the gate, binary verdict.
49
+ - [tests-can-assert-the-bug](tests-can-assert-the-bug.md) — a green test that contradicts a root cause is a suspect.
50
+ - [prove-causation-before-acting](prove-causation-before-acting.md) — observe at the deciding boundary before fixing.
51
+ - [re-read-state-before-acting](re-read-state-before-acting.md) — re-read at the moment you act, not at session start.
52
+ - [gate-changes-on-measurement-not-vibes](gate-changes-on-measurement-not-vibes.md) — eval/golden-set, not intuition.
53
+ - [shift-correctness-left-and-distrust-false-greens](shift-correctness-left-and-distrust-false-greens.md) — pre-push checks; a gate that didn't run is a false green.
54
+
55
+ ### Concurrency & safety
56
+ - [claim-then-act-for-irreversible-ops](claim-then-act-for-irreversible-ops.md) — stamp the claim atomically, then execute.
57
+ - [async-callbacks-verify-liveness](async-callbacks-verify-liveness.md) — confirm the target is still current before mutating it.
58
+ - [enforce-your-guarantee-at-your-boundary](enforce-your-guarantee-at-your-boundary.md) — redact/sanitize/audit/authorize where you emit.
59
+
60
+ ### Data & identity
61
+ - [keep-an-immutable-source-of-truth](keep-an-immutable-source-of-truth.md) — raw immutable; downstream is a replayable transform.
62
+ - [model-identity-with-stable-ids-and-provenance](model-identity-with-stable-ids-and-provenance.md) — join on stable ids; carry source+timestamp.
63
+ - [read-layer-tolerates-unbackfilled-rows](read-layer-tolerates-unbackfilled-rows.md) — handle legacy rows during the backfill window.
64
+
65
+ ### Workflow & git
66
+ - [change-isnt-done-until-every-surface-updated](change-isnt-done-until-every-surface-updated.md) — update every rippled surface in the same PR.
67
+ - [operator-choices-are-first-class](operator-choices-are-first-class.md) — track operator choices; copy criteria verbatim.
68
+ - [co-commit-entangled-work](co-commit-entangled-work.md) — co-commit file-sharing issues; skip already-merged commits on rebase.
69
+ - [stray-control-bytes-hide-changes](stray-control-bytes-hide-changes.md) — control bytes turn files binary and silence review.
70
+ - [long-agent-runs-checkpoint-not-poll](long-agent-runs-checkpoint-not-poll.md) — checkpoint into short runs; end agents at PR-open.
71
+
72
+ ### Ops & deploy
73
+ - [deploy-by-digest-and-verify-the-running-revision](deploy-by-digest-and-verify-the-running-revision.md) — immutable digest; verify the active revision.
74
+ - [iac-first-no-console-changes](iac-first-no-console-changes.md) — version-controlled IaC with state and drift detection.
75
+ - [worktrees-need-bootstrap-from-origin-main](worktrees-need-bootstrap-from-origin-main.md) — branch off origin/main; bootstrap; absolute paths.
76
+ - [shell-safety-pipefail-and-validate-before-teardown](shell-safety-pipefail-and-validate-before-teardown.md) — pipefail; validate the risky step before teardown.
77
+
78
+ ### Tooling
79
+ - [prefer-tool-enforced-structured-output](prefer-tool-enforced-structured-output.md) — schema/tool-enforced output over prompt-policed format.
80
+ - [dependabot-triage-playbook](dependabot-triage-playbook.md) — 6-phase order for clearing a Dependabot pile.
@@ -0,0 +1,15 @@
1
+ # Async callbacks must verify liveness
2
+
3
+ **Trigger:** A callback, webhook, or deferred task fires and mutates the state of a target that may have moved on.
4
+
5
+ **Failure mode:** By the time the callback runs, the target may be stale, superseded, or gone. Writing its state blindly resurrects dead entities or clobbers newer data. In-flight writes that don't gate downstream actions cause ordering bugs.
6
+
7
+ **Correct behavior:**
8
+ - Verify the target is still alive/current before touching its state.
9
+ - Gate downstream actions on the completion of in-flight writes.
10
+ - Make read-modify-write a single atomic statement, not a check-then-write pair.
11
+ - Treat a stale/missing target as a no-op, not an error to force through.
12
+
13
+ **Check:** If the target was deleted/superseded mid-flight, does the callback safely skip instead of writing?
14
+
15
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,15 @@
1
+ # A change isn't done until every surface is updated
2
+
3
+ **Trigger:** You renamed something, changed a signature, or reworked a flow/copy.
4
+
5
+ **Failure mode:** The change ripples further than the diff. Docs, config, fixtures, mock factories, marketing copy, notifications, and E2E specs that assert the old copy/flow still reference the old shape — so half the system is inconsistent and tests pass on stale assumptions.
6
+
7
+ **Correct behavior:**
8
+ - Enumerate every surface the change touches: code call-sites, docs, config, fixtures, mock factories, user-facing copy, notifications.
9
+ - Update all call-sites AND their test assertions AND mock/factory definitions.
10
+ - Update E2E specs that assert the changed copy or flow.
11
+ - Land all of it in the SAME PR, not as follow-ups.
12
+
13
+ **Check:** Grep for the old name/string across the whole repo — zero hits outside history?
14
+
15
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,16 @@
1
+ # Claim, then act, for irreversible operations
2
+
3
+ **Trigger:** A destructive or exactly-once operation (charge, send, delete, provision) that must never run twice.
4
+
5
+ **Failure mode:** Validate-then-destroy leaves a window where two callers both pass validation and both execute. Assuming the provider is idempotent without confirming it means duplicates slip through.
6
+
7
+ **Correct behavior:**
8
+ - Atomically stamp the claim marker FIRST, in the same statement/transaction, before doing the irreversible act.
9
+ - Only proceed if your stamp won the claim.
10
+ - Execute the irreversible act after the claim is held.
11
+ - Clear the marker on failure so legitimate retries can re-claim.
12
+ - Never assume provider-side idempotency; confirm it or enforce your own key.
13
+
14
+ **Check:** If two requests race, does exactly one win the claim and exactly one side-effect occur?
15
+
16
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,15 @@
1
+ # Co-commit entangled work
2
+
3
+ **Trigger:** You're splitting work into one-commit-per-issue, but several issues touch the same files; or rebasing a stacked PR after an upstream squash-merge.
4
+
5
+ **Failure mode:** Forcing artificial 1:1 commit-per-issue when issues share files produces commits that don't build in isolation. Rebasing over a squash-merge re-applies already-merged commits, creating conflicts and phantom changes.
6
+
7
+ **Correct behavior:**
8
+ - Co-commit issues that share files when splitting would break the build; cite ALL issue numbers in the subject.
9
+ - Don't force artificial one-commit-per-issue at the cost of a buildable history.
10
+ - When rebasing a stacked PR after an upstream squash-merge, skip already-applied commits.
11
+ - Verify the final diff contains ONLY new work, nothing already merged.
12
+
13
+ **Check:** Does each commit build, and is the final rebased diff free of already-merged changes?
14
+
15
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,17 @@
1
+ # Dependabot triage playbook
2
+
3
+ **Trigger:** A pile of Dependabot PRs and security alerts has accumulated and you need to clear it without breaking the build.
4
+
5
+ **Failure mode:** Merging blindly in arbitrary order causes conflicts, chases ghost alerts already fixed, and lands risky majors next to coordinated changes — turning a routine chore into a multi-day breakage.
6
+
7
+ **Correct behavior (6 phases, in order):**
8
+ - Phase 0 — Kill noise: regenerate the lockfile to clear ghost/transitive alerts already resolved.
9
+ - Phase 1 — Merge the patch group together.
10
+ - Phase 2 — Address residual real CVEs.
11
+ - Phase 3 — Take safe major bumps.
12
+ - Phase 4 — Risky majors, one per PR.
13
+ - Phase 5 — Docker base-image coordination, last.
14
+
15
+ **Check:** After each phase, is the build green before starting the next?
16
+
17
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,15 @@
1
+ # Deploy by digest, verify the running revision
2
+
3
+ **Trigger:** You deployed, and code that's clearly merged appears missing in production.
4
+
5
+ **Failure mode:** Deploying by a mutable tag (e.g. `:latest`) means the running container may be stale or ambiguous. Without verifying the active revision, "merged but missing" gets misdiagnosed as a code bug when it's actually a deploy/rollout/cache/data-gating issue.
6
+
7
+ **Correct behavior:**
8
+ - Deploy by immutable digest, not a mutable tag.
9
+ - Use a unique revision id per deploy.
10
+ - After every deploy, verify the ACTIVE revision/digest is the one you intended.
11
+ - For "merged but missing", suspect deploy/rollout/cache/data-gating BEFORE suspecting code.
12
+
13
+ **Check:** Does the live revision's digest match the artifact you just built?
14
+
15
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,16 @@
1
+ # Enforce your guarantee at your own boundary
2
+
3
+ **Trigger:** You depend on an upstream contract for safety (redaction, sanitization, auth, audit) at the point where you emit output or take action.
4
+
5
+ **Failure mode:** Trusting that "someone upstream already handled it" leaves gaps: secrets leak because redaction happened earlier and not here; partial sanitization (named entities but not numeric) is bypassable; open redirects; missing audit rows on failure paths; hiding content behind permission checks that the client can ignore.
6
+
7
+ **Correct behavior:**
8
+ - Redact at YOUR boundary; don't trust an upstream's redaction contract.
9
+ - Sanitize/decode to a fixed point, covering BOTH named and numeric entity forms.
10
+ - Validate user-supplied redirect URLs against an allowlist before use.
11
+ - Emit audit rows on EVERY path — success AND failure.
12
+ - Gate controls/affordances by permission; never gate sensitive content by hiding the control alone.
13
+
14
+ **Check:** If every upstream guarantee were removed, would your boundary still be safe on its own?
15
+
16
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,15 @@
1
+ # Gate changes on measurement, not vibes
2
+
3
+ **Trigger:** You're about to adopt a "best practice" or standard technique, or write a claim about a measurable property.
4
+
5
+ **Failure mode:** Intuition and reputation mislead. A technique that's standard everywhere can measurably degrade YOUR corpus. Writing "this meets contrast requirements" or "this improves hit-rate" without computing the number ships a false claim.
6
+
7
+ **Correct behavior:**
8
+ - Gate changes on an eval / golden-set, not on intuition or "everyone does this."
9
+ - If a standard technique degrades your actual metric, reject it (or keep it behind a flag).
10
+ - Compute the real metric (e.g. contrast ratio, hit-rate, accuracy) before asserting it.
11
+ - Keep the eval cheap enough to run on every relevant change.
12
+
13
+ **Check:** Is there a number from your own corpus backing this change, measured before and after?
14
+
15
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,15 @@
1
+ # IaC-first, no console changes
2
+
3
+ **Trigger:** You're about to create or change a cloud resource through a console click or a one-off imperative script.
4
+
5
+ **Failure mode:** Console/bash-script changes have no state, no diff, no review, and drift silently from any written intent. The "source of truth" becomes tribal knowledge, and rebuilding the environment is impossible.
6
+
7
+ **Correct behavior:**
8
+ - Provision every cloud resource via version-controlled IaC (e.g. Terraform/CDK/Pulumi) with managed state.
9
+ - Require plan/diff review before apply.
10
+ - Run drift detection and reconcile drift back into code.
11
+ - Treat an imperative console/script change as not a source of truth — never the system of record.
12
+
13
+ **Check:** Can you rebuild this resource from code alone, and does a plan show zero drift?
14
+
15
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,15 @@
1
+ # Independent, objective review gate
2
+
3
+ **Trigger:** A change is "ready"; the author (human or agent) is signing off on their own work.
4
+
5
+ **Failure mode:** Authors rationalize past their own blind spots — the same assumptions that produced the bug excuse it in review. For visual/CSS changes, reading the stylesheet "looks right" while the rendered page is broken.
6
+
7
+ **Correct behavior:**
8
+ - Have a fresh reviewer with no stake re-review from scratch, not just skim the diff.
9
+ - The reviewer re-runs the gate independently rather than trusting the author's run.
10
+ - Return a binary verdict (pass/fail), not a vibe.
11
+ - For visual changes, inspect the RENDERED result (headless browser, screenshot), never just the source.
12
+
13
+ **Check:** Did someone who didn't write the change independently reproduce the passing state?
14
+
15
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,15 @@
1
+ # Keep an immutable source of truth
2
+
3
+ **Trigger:** You're processing source data into derived artifacts, especially in a pipeline you'll want to improve later.
4
+
5
+ **Failure mode:** Mutating source data in place, or treating derived artifacts as the only copy, means an improvement to the pipeline can't be applied retroactively — you'd have to re-acquire the raw data, which may be gone. Mutating shared/immutable resources corrupts everyone downstream.
6
+
7
+ **Correct behavior:**
8
+ - Store raw source data immutably; never overwrite it.
9
+ - Make every downstream artifact a REPLAYABLE transform of the raw source.
10
+ - Design so reprocessing after an improvement is free (just re-run the transform).
11
+ - Reference shared/immutable resources; never mutate them in place.
12
+
13
+ **Check:** After improving the pipeline, can you regenerate all artifacts from raw without re-fetching anything?
14
+
15
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,15 @@
1
+ # Long agent runs: checkpoint, don't poll
2
+
3
+ **Trigger:** A single agent run is expected to take a long time, or an agent is sitting in a polling loop waiting on a review bot.
4
+
5
+ **Failure mode:** Long single runs die from stale socket reuse (not a clean hard timeout), losing all uncommitted work. Agents that poll review bots burn time and tokens waiting, and block the orchestrator.
6
+
7
+ **Correct behavior:**
8
+ - Checkpoint long work into short runs that commit/push after each unit, so progress survives a death.
9
+ - Don't rely on a single marathon run to finish before something breaks.
10
+ - Terminate implementation agents at PR-open instead of polling for review results.
11
+ - Let the orchestrator handle review follow-up, freeing the implementation agent.
12
+
13
+ **Check:** If the run died right now, is the completed work already committed and pushed?
14
+
15
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,15 @@
1
+ # Model identity with stable ids and provenance
2
+
3
+ **Trigger:** You're joining records, deduping, or attributing data across sources or over time.
4
+
5
+ **Failure mode:** Joining on mutable names/handles breaks when they change or collide, silently merging distinct entities. Dropping provenance means you can't tell where a datum came from or how fresh it is. Guessing an identity to avoid a null fabricates wrong links.
6
+
7
+ **Correct behavior:**
8
+ - Join on stable, unique ids — never on mutable names/handles.
9
+ - Carry source + timestamp provenance on every datum, end-to-end, so the UI can render it.
10
+ - Prefer null over a guessed identity.
11
+ - Surface provenance to users so they can judge freshness and origin.
12
+
13
+ **Check:** For any displayed datum, can you trace its source and timestamp without guessing?
14
+
15
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,15 @@
1
+ # Operator choices are first-class
2
+
3
+ **Trigger:** A planning/orchestration workflow where an operator picks options and acceptance criteria come from a spec.
4
+
5
+ **Failure mode:** Operator-selected options get treated as silent assumptions and quietly dropped. Acceptance criteria get paraphrased and drift from the spec. Grouping/sequencing derived from prose in issue bodies disagrees with the tracked board.
6
+
7
+ **Correct behavior:**
8
+ - Turn every operator-selected option into a first-class, tracked item — never a silent assumption.
9
+ - Copy acceptance criteria VERBATIM from the spec as checkboxes.
10
+ - Use the board's structured Phase field as the authority for grouping, not prose in issue bodies.
11
+ - Make each tracked choice auditable back to the operator decision that produced it.
12
+
13
+ **Check:** Can every plan item be traced to either a spec criterion (verbatim) or a recorded operator choice?
14
+
15
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,15 @@
1
+ # Prefer tool-enforced structured output
2
+
3
+ **Trigger:** You're relying on prompt instructions to make a model produce a specific format, or letting a pipeline proceed on an unknown model/config.
4
+
5
+ **Failure mode:** "Please respond as JSON" is policed by the model's goodwill — it drifts, adds prose, or breaks downstream parsing. Pipelines that silently accept unknown/unpriced models or configs fail unpredictably or rack up surprise cost.
6
+
7
+ **Correct behavior:**
8
+ - Use schema/tool-enforced structured output (e.g. tool-use / function calling / response schemas) instead of prompt-policed formatting.
9
+ - Treat formatting instructions as fragile; enforced structure as reliable.
10
+ - Guard pipelines to refuse to proceed on unknown/unpriced models or configs.
11
+ - Fail loud on a config you can't validate, rather than guessing.
12
+
13
+ **Check:** If the model ignored every formatting instruction, would the output still parse?
14
+
15
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,15 @@
1
+ # Prove causation before acting
2
+
3
+ **Trigger:** You have a plausible explanation — backed by vendor docs or a web search — and you're about to fix based on it.
4
+
5
+ **Failure mode:** A plausible hypothesis is not a diagnosis. Anchoring on an adjacent hop that tests green (the source emitted the right thing) while the real decision happens elsewhere (the destination rejected it) leads to fixing the wrong layer.
6
+
7
+ **Correct behavior:**
8
+ - Isolate one variable at a time and observe its effect directly.
9
+ - Instrument at the DECIDING boundary — read the destination's actual admission/decision, not the source's intent.
10
+ - Don't let "this adjacent step passes" stand in for "the failing step is understood."
11
+ - Reproduce the failure under your hypothesis before committing to the fix.
12
+
13
+ **Check:** Can you show the exact point where good input becomes the bad outcome, observed (not inferred)?
14
+
15
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,14 @@
1
+ # Re-read state at the moment you act
2
+
3
+ **Trigger:** You're about to act on a spec, file, or assumption you read earlier in the session.
4
+
5
+ **Failure mode:** Content reconciles and changes under you between read and act. Acting on a snapshot from session start — or asserting "X doesn't exist" against a stale working tree — produces decisions based on a world that no longer exists.
6
+
7
+ **Correct behavior:**
8
+ - Re-read specs/files at the moment of acting, not just at session start.
9
+ - Verify "X doesn't exist / X still says Y" against origin/main (or current truth), not a possibly-stale local copy.
10
+ - After any reconcile/merge/regen step, re-read before continuing.
11
+
12
+ **Check:** Is the content you're acting on the current version, fetched just now?
13
+
14
+ **Seen in:** recurring across multiple production projects.
@@ -0,0 +1,15 @@
1
+ # Read layer must tolerate un-backfilled rows
2
+
3
+ **Trigger:** A fix ships new code plus a backfill migration for existing rows.
4
+
5
+ **Failure mode:** Code goes live before the backfill finishes. During that deploy window the read layer encounters legacy rows missing the new field/shape and crashes or misbehaves — an outage caused by the fix itself.
6
+
7
+ **Correct behavior:**
8
+ - Assume the read layer will see not-yet-backfilled legacy rows during the deploy window.
9
+ - Default/coalesce missing values safely at read time.
10
+ - Don't require the backfilled shape until the backfill is confirmed complete.
11
+ - Sequence: ship tolerant read code, run backfill, only then tighten if needed.
12
+
13
+ **Check:** Does the new read path work correctly against a row in the OLD shape?
14
+
15
+ **Seen in:** recurring across multiple production projects.