autonomous-sdlc-harness 0.1.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.
- package/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +24 -0
- package/dist/cli.js +194 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/config.js +561 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/daemon.js +791 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/doctor.js +336 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.js +2023 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/registry.js +42 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/config/check.js +505 -0
- package/dist/config/check.js.map +1 -0
- package/dist/config/io.js +177 -0
- package/dist/config/io.js.map +1 -0
- package/dist/config/model.js +406 -0
- package/dist/config/model.js.map +1 -0
- package/dist/core/errors.js +71 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/git.js +537 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/json.js +125 -0
- package/dist/core/json.js.map +1 -0
- package/dist/core/layerCoverage.js +141 -0
- package/dist/core/layerCoverage.js.map +1 -0
- package/dist/core/layerGapRemedy.js +62 -0
- package/dist/core/layerGapRemedy.js.map +1 -0
- package/dist/core/nameList.js +23 -0
- package/dist/core/nameList.js.map +1 -0
- package/dist/core/paths.js +153 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/prompt.js +206 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/repoPaths.js +55 -0
- package/dist/core/repoPaths.js.map +1 -0
- package/dist/core/report.js +150 -0
- package/dist/core/report.js.map +1 -0
- package/dist/core/templating.js +88 -0
- package/dist/core/templating.js.map +1 -0
- package/dist/core/writer.js +479 -0
- package/dist/core/writer.js.map +1 -0
- package/dist/daemon/backend.js +180 -0
- package/dist/daemon/backend.js.map +1 -0
- package/dist/daemon/units.js +380 -0
- package/dist/daemon/units.js.map +1 -0
- package/dist/detect/nestedApplication.js +79 -0
- package/dist/detect/nestedApplication.js.map +1 -0
- package/dist/detect/presets.js +2033 -0
- package/dist/detect/presets.js.map +1 -0
- package/dist/detect/signals.js +1368 -0
- package/dist/detect/signals.js.map +1 -0
- package/dist/doctor/checks.js +3530 -0
- package/dist/doctor/checks.js.map +1 -0
- package/dist/generators/claudeContext.js +588 -0
- package/dist/generators/claudeContext.js.map +1 -0
- package/dist/generators/githooks.js +446 -0
- package/dist/generators/githooks.js.map +1 -0
- package/dist/generators/harnessConfig.js +632 -0
- package/dist/generators/harnessConfig.js.map +1 -0
- package/dist/generators/notifications.js +191 -0
- package/dist/generators/notifications.js.map +1 -0
- package/dist/generators/outerLoopScripts.js +165 -0
- package/dist/generators/outerLoopScripts.js.map +1 -0
- package/dist/generators/permissionProfile.js +1172 -0
- package/dist/generators/permissionProfile.js.map +1 -0
- package/dist/generators/projectSettings.js +322 -0
- package/dist/generators/projectSettings.js.map +1 -0
- package/dist/generators/repoRoot.js +417 -0
- package/dist/generators/repoRoot.js.map +1 -0
- package/dist/generators/scripts.js +557 -0
- package/dist/generators/scripts.js.map +1 -0
- package/dist/generators/stateDir.js +221 -0
- package/dist/generators/stateDir.js.map +1 -0
- package/dist/machine/paths.js +111 -0
- package/dist/machine/paths.js.map +1 -0
- package/dist/machine/plugins.js +224 -0
- package/dist/machine/plugins.js.map +1 -0
- package/dist/machine/registry.js +330 -0
- package/dist/machine/registry.js.map +1 -0
- package/package.json +23 -0
- package/scripts/README.md +13 -0
- package/scripts/daemon/launchd.plist.template +59 -0
- package/scripts/daemon/systemd.service.template +58 -0
- package/templates/README.md +15 -0
- package/templates/claude/CLAUDE.md +54 -0
- package/templates/claude/README.md +5 -0
- package/templates/claude/context/api.md +29 -0
- package/templates/claude/context/conventions.md +23 -0
- package/templates/claude/context/data-layer.md +28 -0
- package/templates/claude/context/data-storage.md +29 -0
- package/templates/claude/context/docs-catalog.md +29 -0
- package/templates/claude/context/domain.md +28 -0
- package/templates/claude/context/layer.md +20 -0
- package/templates/claude/context/module.md +30 -0
- package/templates/claude/context/package.md +29 -0
- package/templates/claude/context/presentation.md +32 -0
- package/templates/claude/context/state-slices.md +28 -0
- package/templates/claude/context/tests.md +28 -0
- package/templates/claude/harness-task-offer.md +58 -0
- package/templates/claude/push-notify.env.example +21 -0
- package/templates/claude/qa-accounts.env.example +38 -0
- package/templates/claude/qa_test_scenarios.md +110 -0
- package/templates/claude/settings.autonomous.json +93 -0
- package/templates/claude/settings.autonomous.qa.json +36 -0
- package/templates/githooks/README.md +3 -0
- package/templates/githooks/pre-push +72 -0
- package/templates/repo/README.md +3 -0
- package/templates/repo/gitattributes +16 -0
- package/templates/repo/gitignore +61 -0
- package/templates/repo/gitignore.qa +25 -0
- package/templates/repo/mcp.json +17 -0
- package/templates/scripts/README.md +5 -0
- package/templates/scripts/autonomous-format-stream.sh +95 -0
- package/templates/scripts/autonomous-notify.sh +337 -0
- package/templates/scripts/autonomous-watcher.sh +3087 -0
- package/templates/scripts/cleanup-merged-worktrees.sh +327 -0
- package/templates/scripts/commit-on-branch.sh +288 -0
- package/templates/scripts/create-worktree.sh +360 -0
- package/templates/scripts/deploy.sh +47 -0
- package/templates/scripts/lib/harness-run-lib.sh +1481 -0
- package/templates/scripts/push-branch.sh +140 -0
- package/templates/scripts/refresh-branch.sh +244 -0
- package/templates/scripts/restart-watcher.sh +401 -0
- package/templates/scripts/scratch-run.sh +302 -0
- package/templates/scripts/setup-worktree.sh +262 -0
- package/templates/scripts/start-dev-server.sh +99 -0
- package/templates/scripts/test.sh +50 -0
- package/templates/scripts/typecheck.sh +50 -0
- package/templates/state-dir/README-root.md +13 -0
- package/templates/state-dir/README.md +9 -0
- package/templates/state-dir/architecture_branch_review_point_reviews/README.md +9 -0
- package/templates/state-dir/architecture_branch_reviews/README.md +9 -0
- package/templates/state-dir/architecture_reviews/README.md +9 -0
- package/templates/state-dir/architecture_user_review_reviews/README.md +9 -0
- package/templates/state-dir/autonomous_inbox/README.md +9 -0
- package/templates/state-dir/autonomous_logs/README.md +9 -0
- package/templates/state-dir/branch_statistics/README.md +9 -0
- package/templates/state-dir/business_parity_branch_review_point_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_branch_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_user_review_reviews/README.md +9 -0
- package/templates/state-dir/clarification_digests/README.md +9 -0
- package/templates/state-dir/clarifications/README.md +9 -0
- package/templates/state-dir/code_reviews/README.md +9 -0
- package/templates/state-dir/dispatch_additions/README.md +19 -0
- package/templates/state-dir/docs_catalog/README.md +9 -0
- package/templates/state-dir/flow_progress/README.md +9 -0
- package/templates/state-dir/improvement_observations/README.md +19 -0
- package/templates/state-dir/improvement_suggestions.md +29 -0
- package/templates/state-dir/lessons.md +23 -0
- package/templates/state-dir/qa_review_point_reviews/README.md +9 -0
- package/templates/state-dir/qa_reviews/README.md +9 -0
- package/templates/state-dir/review_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/review_plan_reviews/README.md +9 -0
- package/templates/state-dir/scratch/README.md +11 -0
- package/templates/state-dir/skeptic_review_plan_reviews/README.md +9 -0
- package/templates/state-dir/skeptic_review_point_reviews/README.md +9 -0
- package/templates/state-dir/skeptic_reviews/README.md +9 -0
- package/templates/state-dir/story_plans/README.md +9 -0
- package/templates/state-dir/task_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/task_plan_reviews/README.md +9 -0
- package/templates/state-dir/task_plans/README.md +9 -0
- package/templates/state-dir/task_prompts/README.md +9 -0
- package/templates/state-dir/ui_test_plan_reviews/README.md +9 -0
- package/templates/state-dir/ui_test_plans/README.md +9 -0
- package/templates/state-dir/user_review_fix_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/user_reviews/README.md +9 -0
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generator: the always-loaded project file, the conventions stubs behind it, and the two
|
|
3
|
+
* `*.env.example` companions an adopter copies and completes.
|
|
4
|
+
*
|
|
5
|
+
* **The rule this module exists to enforce: which stubs are written, and which rows the routing
|
|
6
|
+
* table carries, are both read off `layers[]` — never off a list kept here.** `layers[].conventions`
|
|
7
|
+
* is "the document holding this layer's rules" (`schemas/harness.config.schema.json`), and the
|
|
8
|
+
* orchestrator assigns every task to exactly one layer, so a fixed file list here would put a
|
|
9
|
+
* skeleton at a path no layer points at while leaving the path one *does* point at empty. A
|
|
10
|
+
* generated table that disagrees with the configuration it was generated from is worse than no
|
|
11
|
+
* table: an agent follows the pointer rather than the config.
|
|
12
|
+
*
|
|
13
|
+
* ## Three non-obvious choices, and where each comes from
|
|
14
|
+
*
|
|
15
|
+
* 1. **The templates live at `templates/claude/…`, without the dot, and `init` adds it.** They are
|
|
16
|
+
* templates for *someone else's* configuration, so the dot belongs at the adopter's end: `init`
|
|
17
|
+
* adds it — here via `CLAUDE_DIR`, and in the sibling generators that declare their own — and no
|
|
18
|
+
* stored template path carries it. Not because a dotted path here would register them as live
|
|
19
|
+
* assets: that reason was withdrawn as measurably false (`cli/templates/claude/README.md`,
|
|
20
|
+
* `cli/templates/README.md`). This is the generator where that distinction becomes load-bearing
|
|
21
|
+
* rather than decorative.
|
|
22
|
+
* 2. **Every file here is `create-if-absent`, and the analyze command is why.** These are skeletons
|
|
23
|
+
* that a later pass fills in from the adopted repository's real code, and the adopter edits them
|
|
24
|
+
* afterwards; a second `init` that rewrote one would undo both. That is the write engine's own
|
|
25
|
+
* row for this artifact (`core/writer.ts`), and `--force` still takes a `.bak` first.
|
|
26
|
+
* 3. **The two `.env.example` files are written beside the file they tell you to create** — at
|
|
27
|
+
* `<configured path>.example` — rather than at a fixed name. Their whole content is "copy me to
|
|
28
|
+
* the path `pushEnvPath` / `qa.credentialsPath` names"; written anywhere else, that instruction
|
|
29
|
+
* points across the tree. With the paths `init` itself seeds, both land in `.claude/`, which is
|
|
30
|
+
* where the template tree says a `claude/` template goes.
|
|
31
|
+
*
|
|
32
|
+
* ## What this generator deliberately does not do
|
|
33
|
+
*
|
|
34
|
+
* - **It does not restate the project-command wrappers.** `commands.*` in `harness.config.json` is
|
|
35
|
+
* the canonical home for those invocations, and the config, the wrapper files and the permission
|
|
36
|
+
* profile are already three views of one string (`generators/scripts.ts`); an always-loaded file
|
|
37
|
+
* repeating them would be a fourth, drifting the moment `scriptsDir` moves.
|
|
38
|
+
* - **It does not invent credential key names.** The two example files carry harness-generic keys —
|
|
39
|
+
* `HARNESS_PUSH_*`, `HARNESS_QA_<n>_*` — and say so in their own header comments, so an adopter
|
|
40
|
+
* carrying settings over from another runner renames the keys rather than the reader.
|
|
41
|
+
*/
|
|
42
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
43
|
+
import { join, posix } from 'node:path';
|
|
44
|
+
import { DEFAULTS } from '../config/model.js';
|
|
45
|
+
import { defaultProjectName, readTemplate } from '../core/paths.js';
|
|
46
|
+
import { normalizeRepoPathStrict } from '../core/repoPaths.js';
|
|
47
|
+
import { renderTemplate } from '../core/templating.js';
|
|
48
|
+
import { SHARED_CONVENTIONS_PATH } from '../detect/presets.js';
|
|
49
|
+
import { PUSH_ENV_PATH, QA_CREDENTIALS_PATH } from './harnessConfig.js';
|
|
50
|
+
import { PLUGIN_NAME } from './projectSettings.js';
|
|
51
|
+
/** The adopter-side directory this whole template subtree lands in. The dot is `init`-side only. */
|
|
52
|
+
const CLAUDE_DIR = '.claude';
|
|
53
|
+
/**
|
|
54
|
+
* The always-loaded project file, relative to the repository root.
|
|
55
|
+
*
|
|
56
|
+
* Exported because two readers outside this module address the same file: `init` reads it before the
|
|
57
|
+
* plan is applied, to resolve the analyze offer and to decide which of its closing wordings is true,
|
|
58
|
+
* and `doctor`'s `setup-analysis` check reads it for the {@link SETUP_PENDING_OPEN} banner. Both
|
|
59
|
+
* import this rather than re-spelling `.claude/CLAUDE.md`, for the reason the markers below are
|
|
60
|
+
* exported: a second copy of a literal is a copy that can drift.
|
|
61
|
+
*/
|
|
62
|
+
export const CLAUDE_MD_PATH = `${CLAUDE_DIR}/CLAUDE.md`;
|
|
63
|
+
/**
|
|
64
|
+
* The project's test-scenario rules, relative to the repository root — written only when the
|
|
65
|
+
* interactive test phase is on.
|
|
66
|
+
*
|
|
67
|
+
* **The basename is carried over from the instruction corpus unchanged, and renaming it here is a
|
|
68
|
+
* breaking change.** The shipped QA assets — the test instructions, and the agents that plan and run
|
|
69
|
+
* the interactive phase — dereference this document by that exact name rather than through a config
|
|
70
|
+
* key, so a different name here leaves every one of them pointing at a file nothing writes. It is
|
|
71
|
+
* `create-if-absent` like every other `.claude/` artifact: the skeleton is the adopter's from the
|
|
72
|
+
* moment it lands, and the rules they fill in are the only copy.
|
|
73
|
+
*/
|
|
74
|
+
export const QA_SCENARIOS_PATH = `${CLAUDE_DIR}/qa_test_scenarios.md`;
|
|
75
|
+
/**
|
|
76
|
+
* The change-request offer dialogue, relative to the repository root — planned on every run, whatever the
|
|
77
|
+
* configured phases are, and created where it is absent.
|
|
78
|
+
*
|
|
79
|
+
* **The basename is dereferenced by name**, from the always-loaded project file's
|
|
80
|
+
* `## Where a change request runs` section, so renaming it here leaves that pointer aimed at a file
|
|
81
|
+
* nothing writes and silently disables the offer. Exported because `doctor` addresses the same file
|
|
82
|
+
* rather than re-spelling it.
|
|
83
|
+
*/
|
|
84
|
+
export const TASK_OFFER_PATH = `${CLAUDE_DIR}/harness-task-offer.md`;
|
|
85
|
+
/** The templates' subdirectory under `cli/templates/`, addressed as {@link readTemplate} wants it. */
|
|
86
|
+
const TEMPLATE_DIR = 'claude';
|
|
87
|
+
/** The context stubs' subdirectory inside {@link TEMPLATE_DIR}. */
|
|
88
|
+
const CONTEXT_TEMPLATE_DIR = 'context';
|
|
89
|
+
/**
|
|
90
|
+
* The stubs that have a skeleton of their own, keyed by the **basename** a layer's `conventions`
|
|
91
|
+
* path ends in. Each name is also its template's file name under `templates/claude/context/`, so a
|
|
92
|
+
* stub is added by dropping one template in beside its siblings and adding its name here.
|
|
93
|
+
*
|
|
94
|
+
* A configured conventions path matching none of these gets {@link GENERIC_STUB_TEMPLATE} instead:
|
|
95
|
+
* the layer still needs a document at the path the config promises, and a generic skeleton naming
|
|
96
|
+
* the layer is more use to its implementer than a missing file.
|
|
97
|
+
*
|
|
98
|
+
* **The set is not a list of what `init` generates, and is deliberately larger than one.** Two
|
|
99
|
+
* groups:
|
|
100
|
+
*
|
|
101
|
+
* - **Reached by a preset.** `conventions.md` (every preset's `general` layer), `data-layer.md`,
|
|
102
|
+
* `domain.md`, `presentation.md` (`layered-clean-arch`), `api.md` (`api-service`) and
|
|
103
|
+
* `package.md` (`python-package`), `module.md` (every preset whose layer profile is a conventional
|
|
104
|
+
* source root — `cmake-cpp` points both of its rows at it), and `tests.md` — the document every preset's **test** layer points at, reached by
|
|
105
|
+
* `flutter`, `jvm`, `android-gradle`, `apple-native`, `rust-cargo`, `dotnet`, `php-composer`,
|
|
106
|
+
* `ruby-bundler`, `python-package`, `cmake-cpp`, `layered-clean-arch` and `api-service` whenever
|
|
107
|
+
* the conventional test root of that preset's toolchain exists — the basenames
|
|
108
|
+
* `detect/presets.ts` can produce. Every one of
|
|
109
|
+
* them must be here and must have a template: a preset's own layer falling through to the generic
|
|
110
|
+
* skeleton is the whole point of the preset lost, and `test/init.test.mjs` asserts the pairing
|
|
111
|
+
* over each preset rather than leaving it to inspection.
|
|
112
|
+
* - **Reachable only by hand.** `data-storage.md`, `docs-catalog.md` and `state-slices.md` are
|
|
113
|
+
* skeletons for three concerns no preset detects — local persistence, a documentation catalog,
|
|
114
|
+
* shared application state. Nothing `init` generates points at them, and the one route that reads
|
|
115
|
+
* one is an adopter pointing a `layers[].conventions` at that exact basename and re-running
|
|
116
|
+
* `init`. A path a `/harness-analyze` layer-profile revision newly names does **not** reach them:
|
|
117
|
+
* that command never re-runs `init`, so `conventions-writer` composes the document with no file
|
|
118
|
+
* behind it. They are kept rather than deleted because writing one of those documents from
|
|
119
|
+
* nothing is the expensive part, and they are named here so a later reader does not take them for
|
|
120
|
+
* dead templates.
|
|
121
|
+
*/
|
|
122
|
+
const DEDICATED_STUBS = new Set([
|
|
123
|
+
'api.md',
|
|
124
|
+
'conventions.md',
|
|
125
|
+
'data-layer.md',
|
|
126
|
+
'data-storage.md',
|
|
127
|
+
'docs-catalog.md',
|
|
128
|
+
'domain.md',
|
|
129
|
+
'module.md',
|
|
130
|
+
'package.md',
|
|
131
|
+
'presentation.md',
|
|
132
|
+
'state-slices.md',
|
|
133
|
+
'tests.md',
|
|
134
|
+
]);
|
|
135
|
+
/** The skeleton rendered for a layer whose conventions basename is in none of those above. */
|
|
136
|
+
const GENERIC_STUB_TEMPLATE = 'layer.md';
|
|
137
|
+
/**
|
|
138
|
+
* The two halves of the **untouched-skeleton test**, which is how anything in this tree answers
|
|
139
|
+
* "has anybody filled this document in yet" — the marker every shipped skeleton carries as its last
|
|
140
|
+
* line, and the guidance block every shipped skeleton carries in its body.
|
|
141
|
+
*
|
|
142
|
+
* **They are read together, always, and the marker alone decides nothing.** Each skeleton's footer
|
|
143
|
+
* tells a hand-writer to *replace everything above* it, so an adopter who wrote their rules by hand
|
|
144
|
+
* has removed the guidance block whether or not they thought to delete a trailing HTML comment.
|
|
145
|
+
* Testing the marker alone would report that finished document as unfilled forever, and the only
|
|
146
|
+
* remedy on offer would be the analyze command that adopter declined — which is the path this
|
|
147
|
+
* harness deliberately keeps open.
|
|
148
|
+
*
|
|
149
|
+
* Read by: `doctor`'s `setup-analysis` check, `init`'s "is anything left to fill" question, and the
|
|
150
|
+
* analyze command's decision about whether it may write a target without asking (it clears the
|
|
151
|
+
* marker when it writes). The literal strings also live in every skeleton under
|
|
152
|
+
* `templates/claude/context/` — `ls cli/templates/claude/context/` enumerates them — which is
|
|
153
|
+
* where they are produced: a template and these constants disagreeing makes every consumer report
|
|
154
|
+
* every document as filled.
|
|
155
|
+
*/
|
|
156
|
+
export const UNFILLED_STUB_MARKER = '<!-- harness:unfilled -->';
|
|
157
|
+
/** The other half of the two-part test in {@link UNFILLED_STUB_MARKER}; never applied on its own. */
|
|
158
|
+
export const SKELETON_GUIDANCE_MARKER = '**What belongs here**';
|
|
159
|
+
/**
|
|
160
|
+
* The **untouched-skeleton test** as one predicate over one document's text: both halves above
|
|
161
|
+
* present, read together and never apart.
|
|
162
|
+
*
|
|
163
|
+
* Exported so a consumer that already holds the text applies the conjunction rather than re-spelling
|
|
164
|
+
* it — `init`'s pre-plan read does, and so does the `.bak` decision in {@link writeClaudeContext}
|
|
165
|
+
* below. It deliberately answers about **text only**: what an unreadable file means differs by call
|
|
166
|
+
* site — a document `init` is about to create counts as untouched there, and a `.bak` that is not
|
|
167
|
+
* there protects nothing here — so each caller decides that for itself.
|
|
168
|
+
*/
|
|
169
|
+
export function isUntouchedSkeletonText(content) {
|
|
170
|
+
return content.includes(UNFILLED_STUB_MARKER) && content.includes(SKELETON_GUIDANCE_MARKER);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The analyze targets that are **not** layer names: the layer profile itself, the always-loaded
|
|
174
|
+
* project file, and the shared cross-layer document that is generated whether or not a layer points
|
|
175
|
+
* at it (`docs/analyze.md` §1).
|
|
176
|
+
*
|
|
177
|
+
* Read by: the invocation this generator renders into each stub footer, and `init`'s closing pointer.
|
|
178
|
+
* The config schema's `name` pattern admits all three, so a layer may legitimately carry one — that
|
|
179
|
+
* layer loses the one-word invocation to the reserved meaning, visibly rather than silently, and is
|
|
180
|
+
* still covered by the no-argument pass.
|
|
181
|
+
*/
|
|
182
|
+
export const RESERVED_ANALYZE_TARGETS = ['project', 'conventions', 'layers'];
|
|
183
|
+
/** The command each stub footer points at, argument appended. */
|
|
184
|
+
const ANALYZE_COMMAND = '/harness-analyze';
|
|
185
|
+
/**
|
|
186
|
+
* The delimiters of the **setup-pending banner** the generated project file carries under its
|
|
187
|
+
* heading: the record of what the adopter answered `autonomous-sdlc-harness init`'s analyze offer
|
|
188
|
+
* (`docs/analyze.md` §9), written into the one file every session in that repository loads without
|
|
189
|
+
* being asked.
|
|
190
|
+
*
|
|
191
|
+
* Read by two consumers outside this module, both on these exact literals: `doctor`'s
|
|
192
|
+
* `setup-analysis` check detects the open marker and reports the setup as unfinished, and the
|
|
193
|
+
* analyze command deletes the block between the two inclusive as its last act.
|
|
194
|
+
*
|
|
195
|
+
* **The banner is not a guaranteed record of the answer on a re-run.** {@link CLAUDE_MD_PATH} is
|
|
196
|
+
* enqueued `create-if-absent`, with `WriteRequest.forceOverride: 'never'` on the one case
|
|
197
|
+
* {@link keepsIdenticalProjectFile} computes, so there are three outcomes and not two. *Created*: the
|
|
198
|
+
* banner is in the file. *Kept* — an unforced re-run over a file that is already there, or a forced
|
|
199
|
+
* one over a file already byte-identical to the text this run renders — nothing is written into it;
|
|
200
|
+
* the unforced route says so in a note of {@link writeClaudeContext}'s own and the forced one in
|
|
201
|
+
* `init`'s closing sentence, and that report is what carries the answer on either.
|
|
202
|
+
* *Overwritten under `--force`*: the
|
|
203
|
+
* whole file is regenerated from the template after its single `.bak`, so the banner is
|
|
204
|
+
* **re-inserted** and `doctor`'s `setup-analysis` warning re-opens until the command is run again or
|
|
205
|
+
* the block is deleted by hand. The standing readers of what is still unfilled are that check and
|
|
206
|
+
* {@link UNFILLED_STUB_MARKER}'s two-part test — never this banner.
|
|
207
|
+
*/
|
|
208
|
+
export const SETUP_PENDING_OPEN = '<!-- harness:setup-pending -->';
|
|
209
|
+
/** The banner's closing delimiter: the block {@link SETUP_PENDING_OPEN} documents ends here. */
|
|
210
|
+
export const SETUP_PENDING_CLOSE = '<!-- /harness:setup-pending -->';
|
|
211
|
+
/**
|
|
212
|
+
* Where the command named in both wordings comes from — a **fact about this repository's state**, not
|
|
213
|
+
* an instruction to install anything.
|
|
214
|
+
*
|
|
215
|
+
* Without it the reader who reaches the wrong answer is the adopter who opens a session before the
|
|
216
|
+
* plugin resolves — a clone that never ran `init`, a runner pointed elsewhere — and meets a banner
|
|
217
|
+
* naming a command that is not there, with nothing in the block saying what would have to be true for
|
|
218
|
+
* it to exist. Naming no marketplace and no install step keeps it a fact: `init` already merged the
|
|
219
|
+
* plugin key into the settings file this clause names, so the state it reports is one the same run
|
|
220
|
+
* left behind.
|
|
221
|
+
*/
|
|
222
|
+
const PLUGIN_ORIGIN_CLAUSE = `\`${ANALYZE_COMMAND}\` is a command of the harness plugin, which \`npx autonomous-sdlc-harness init\` enabled for this repository in \`${CLAUDE_DIR}/settings.json\`, so it exists in a session that resolves that plugin.`;
|
|
223
|
+
/**
|
|
224
|
+
* The banner's body, one blockquote per answer.
|
|
225
|
+
*
|
|
226
|
+
* **Each wording ends by stating its own disposal** (`docs/analyze.md` §10): the command removes the
|
|
227
|
+
* block, or the adopter deletes it once these sections are written. This file is loaded on every turn
|
|
228
|
+
* of every session in that repository and no later unforced run revises the block, so a wording
|
|
229
|
+
* without that sentence leaves the adopter who *declined* carrying "setup is not finished" — and
|
|
230
|
+
* `doctor`'s matching warning — with the only route out being the command they turned down.
|
|
231
|
+
*
|
|
232
|
+
* Both wordings carry {@link PLUGIN_ORIGIN_CLAUSE}, and it sits **before** each disposal sentence so
|
|
233
|
+
* neither ends on anything else: the decliner who changes their mind reaches for the same command as
|
|
234
|
+
* the accepter, so a clause on one arm only would leave the other reader with the wrong answer.
|
|
235
|
+
*
|
|
236
|
+
* Neither wording says when the pass will run, neither presents the command as the only route, and
|
|
237
|
+
* each states the repository's setup state rather than addressing an order to whoever reads it next.
|
|
238
|
+
*/
|
|
239
|
+
const SETUP_PENDING_BODY = {
|
|
240
|
+
accepted: `> **Setup is not finished.** The sections below are skeletons, and \`${ANALYZE_COMMAND}\` was accepted at setup: running it is this session's first action. ${PLUGIN_ORIGIN_CLAUSE} The command removes this block when it has run — or the block can be deleted by hand once these sections are written.`,
|
|
241
|
+
declined: `> **Setup is not finished.** The sections below are skeletons, and the analysis was declined at setup: they are yours to write by hand, and \`${ANALYZE_COMMAND}\` still fills them if you change your mind. ${PLUGIN_ORIGIN_CLAUSE} In either case, the block can be deleted by hand once these sections are written.`,
|
|
242
|
+
};
|
|
243
|
+
/** The delimited block rendered into the template's `{{setupBanner}}` slot. */
|
|
244
|
+
function setupBanner(offer) {
|
|
245
|
+
return `${SETUP_PENDING_OPEN}\n${SETUP_PENDING_BODY[offer]}\n${SETUP_PENDING_CLOSE}`;
|
|
246
|
+
}
|
|
247
|
+
/** The ledger the routing table links to, at the root of the configured `stateDir`. */
|
|
248
|
+
const LESSONS_LEDGER = 'lessons.md';
|
|
249
|
+
/** Suffix distinguishing the committed example from the machine-local file it is copied to. */
|
|
250
|
+
const EXAMPLE_SUFFIX = '.example';
|
|
251
|
+
/**
|
|
252
|
+
* A link from `.claude/CLAUDE.md` to a repo-relative target — `context/data-layer.md` for a
|
|
253
|
+
* conventions file in the usual place, `../sdlc-harness/lessons.md` for a ledger outside it.
|
|
254
|
+
*
|
|
255
|
+
* Relative rather than repo-relative because the link is read from the file it sits in, and a
|
|
256
|
+
* conventions path may legitimately point anywhere in the repository.
|
|
257
|
+
*/
|
|
258
|
+
function linkFromClaudeDir(repoRelative) {
|
|
259
|
+
const link = posix.relative(CLAUDE_DIR, repoRelative);
|
|
260
|
+
return link === '' ? repoRelative : link;
|
|
261
|
+
}
|
|
262
|
+
/** `a`, `a and b`, `a, b and c` — for a description naming the layers that share one document. */
|
|
263
|
+
function joinPhrases(parts) {
|
|
264
|
+
if (parts.length <= 1)
|
|
265
|
+
return parts[0] ?? '';
|
|
266
|
+
return `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}`;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The conventions documents to write, deduplicated by path and in the order the routing table
|
|
270
|
+
* lists them: the shared cross-layer document first, then one entry per distinct
|
|
271
|
+
* `layers[].conventions` value in `layers[]` order.
|
|
272
|
+
*
|
|
273
|
+
* **The shared document is always present, even when no layer points at it.** It holds the rules
|
|
274
|
+
* that span every layer — the flow between them, where a new responsibility goes, the testing bar
|
|
275
|
+
* — so every other stub is written on the assumption that it exists, and `CLAUDE.md` gives it a
|
|
276
|
+
* row whatever the layer list looks like. Where a layer *does* point at it (every preset's
|
|
277
|
+
* `general` layer does), that layer joins this entry rather than creating a second one.
|
|
278
|
+
*/
|
|
279
|
+
function collectStubs(config) {
|
|
280
|
+
const byPath = new Map();
|
|
281
|
+
const sharedPath = normalizeRepoPathStrict(SHARED_CONVENTIONS_PATH);
|
|
282
|
+
byPath.set(sharedPath, { path: sharedPath, layers: [] });
|
|
283
|
+
for (const layer of config.layers) {
|
|
284
|
+
const path = normalizeRepoPathStrict(layer.conventions);
|
|
285
|
+
const entry = byPath.get(path);
|
|
286
|
+
if (entry === undefined)
|
|
287
|
+
byPath.set(path, { path, layers: [layer] });
|
|
288
|
+
else
|
|
289
|
+
entry.layers.push(layer);
|
|
290
|
+
}
|
|
291
|
+
return [...byPath.values()].map(({ path, layers }) => {
|
|
292
|
+
const basename = posix.basename(path);
|
|
293
|
+
const dedicated = DEDICATED_STUBS.has(basename);
|
|
294
|
+
return {
|
|
295
|
+
path,
|
|
296
|
+
template: `${CONTEXT_TEMPLATE_DIR}/${dedicated ? basename : GENERIC_STUB_TEMPLATE}`,
|
|
297
|
+
dedicated,
|
|
298
|
+
layers,
|
|
299
|
+
};
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/** The file's text, or `undefined` when there is nothing readable at that path. */
|
|
303
|
+
function readIfPossible(path) {
|
|
304
|
+
try {
|
|
305
|
+
return readFileSync(path, 'utf8');
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Whether a forced run must **keep** this conventions document, because replacing it would destroy
|
|
313
|
+
* the analysis rather than back it up.
|
|
314
|
+
*
|
|
315
|
+
* The engine's `.bak` is single-generation — overwritten, never chained (`core/writer.ts`) — so a
|
|
316
|
+
* *second* forced run over an already-skeletonised corpus replaces a `.bak` holding the filled
|
|
317
|
+
* document with one holding the skeleton, and no copy of the analysis is left anywhere. Both
|
|
318
|
+
* conjuncts below are what keeps this to that case:
|
|
319
|
+
*
|
|
320
|
+
* - **The `.bak` exists and is not itself an untouched skeleton.** Without it there is nothing to
|
|
321
|
+
* protect: a first forced run writes the filled document into the `.bak` and loses nothing.
|
|
322
|
+
* - **The document on disk exists and *is* an untouched skeleton.** Without it a forced run could
|
|
323
|
+
* never put a *filled* document back to a skeleton, which is a legitimate thing to want — and on
|
|
324
|
+
* that run the `.bak` this write produces holds the filled document, so nothing is lost.
|
|
325
|
+
*
|
|
326
|
+
* Any read that fails means no override and the ordinary forced behaviour: this is a protection
|
|
327
|
+
* layered on top of the existing backup, not a gate that may refuse a run.
|
|
328
|
+
*/
|
|
329
|
+
function keepsRescuedBackup(repoRoot, repoRelative) {
|
|
330
|
+
const target = join(repoRoot, repoRelative);
|
|
331
|
+
const backup = readIfPossible(`${target}.bak`);
|
|
332
|
+
if (backup === undefined || isUntouchedSkeletonText(backup))
|
|
333
|
+
return false;
|
|
334
|
+
const current = readIfPossible(target);
|
|
335
|
+
return current !== undefined && isUntouchedSkeletonText(current);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Whether a forced run must **keep** the always-loaded project file, because regenerating it would
|
|
339
|
+
* put nothing in the `.bak` and destroy whatever is already there.
|
|
340
|
+
*
|
|
341
|
+
* **The test is byte-identity with the text this run would write, and it is the right one for this
|
|
342
|
+
* file specifically.** It carries neither {@link UNFILLED_STUB_MARKER} nor
|
|
343
|
+
* {@link SKELETON_GUIDANCE_MARKER}, so {@link isUntouchedSkeletonText} — the two-part test
|
|
344
|
+
* {@link keepsRescuedBackup} applies to a conventions document — answers nothing about it. Equality
|
|
345
|
+
* with the rendered text is the exact statement of both halves of the loss: the `.bak` this write
|
|
346
|
+
* would produce receives nothing the template does not already carry, and the `.bak` already there —
|
|
347
|
+
* single-generation, overwritten rather than chained (`core/writer.ts`) — is destroyed to make room
|
|
348
|
+
* for it.
|
|
349
|
+
*
|
|
350
|
+
* A read that fails means no override and the ordinary forced behaviour, as in
|
|
351
|
+
* {@link keepsRescuedBackup}: this protects a backup, it never refuses a run.
|
|
352
|
+
*/
|
|
353
|
+
function keepsIdenticalProjectFile(repoRoot, rendered) {
|
|
354
|
+
return readIfPossible(join(repoRoot, CLAUDE_MD_PATH)) === rendered;
|
|
355
|
+
}
|
|
356
|
+
/** The routing table's `When working on…` cell for the shared cross-layer document. */
|
|
357
|
+
function sharedConventionsDescription(layers) {
|
|
358
|
+
const base = '**Any implementation, review or planning** — the rules that hold in every layer';
|
|
359
|
+
if (layers.length === 0)
|
|
360
|
+
return base;
|
|
361
|
+
const names = joinPhrases(layers.map((layer) => `\`${layer.name}\``));
|
|
362
|
+
return `${base}, and the ${names} ${layers.length === 1 ? "layer's" : "layers'"} own rules`;
|
|
363
|
+
}
|
|
364
|
+
/** The routing table's `When working on…` cell for one layer's document. */
|
|
365
|
+
function layerDescription(layers) {
|
|
366
|
+
const names = joinPhrases(layers.map((layer) => `\`${layer.name}\``));
|
|
367
|
+
const paths = [...new Set(layers.map((layer) => (layer.path === '.' ? 'the repository root' : `\`${layer.path}\``)))];
|
|
368
|
+
return `The ${names} ${layers.length === 1 ? 'layer' : 'layers'} — ${joinPhrases(paths)}`;
|
|
369
|
+
}
|
|
370
|
+
/** One row of the routing table: a description, and a link that is also its own label. */
|
|
371
|
+
function routingRow(description, link) {
|
|
372
|
+
return `| ${description} | [\`${link}\`](${link}) |`;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* The routing table's body — one row per conventions document, then the lessons ledger.
|
|
376
|
+
*
|
|
377
|
+
* Built from {@link collectStubs}' own list rather than from a second walk of `layers[]`, so a
|
|
378
|
+
* document that is written always has a row and a row never points at a document that is not.
|
|
379
|
+
*/
|
|
380
|
+
function routingRows(stubs, sharedPath, stateDir) {
|
|
381
|
+
const rows = stubs.map((stub) => routingRow(stub.path === sharedPath ? sharedConventionsDescription(stub.layers) : layerDescription(stub.layers), linkFromClaudeDir(stub.path)));
|
|
382
|
+
rows.push(routingRow('**Planning or reviewing at branch level** — the lessons ledger: one-line rules distilled from defects that got past every automated gate and were caught by a human', linkFromClaudeDir(posix.join(stateDir, LESSONS_LEDGER))));
|
|
383
|
+
return rows.join('\n');
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Render a context template through {@link renderTemplate}.
|
|
387
|
+
*
|
|
388
|
+
* **`assertNoneSurvive` is on**, and deliberately: every value substituted here is a path, a layer
|
|
389
|
+
* name, a manifest-mirrored slug or generated markup, none of which has any business carrying
|
|
390
|
+
* `{{…}}`. One that did would ship a half-rendered always-loaded file — the one file every session
|
|
391
|
+
* reads without being asked.
|
|
392
|
+
*/
|
|
393
|
+
function render(template, values) {
|
|
394
|
+
return renderTemplate(readTemplate(`${TEMPLATE_DIR}/${template}`), values, {
|
|
395
|
+
describe: `the context template ${template}`,
|
|
396
|
+
assertNoneSurvive: true,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* The invocation one stub's footer tells its reader to run, and the warning a reserved name earns.
|
|
401
|
+
*
|
|
402
|
+
* The vocabulary is the **layer name** rather than the document's path (`docs/analyze.md` §1): a
|
|
403
|
+
* path vocabulary cannot express the two targets that name no file. Three cases, in order:
|
|
404
|
+
*
|
|
405
|
+
* - **No layer points here.** Only the shared cross-layer document reaches this, and its target is
|
|
406
|
+
* the reserved word `conventions` — the name that exists precisely because it is generated
|
|
407
|
+
* whether or not a layer names it.
|
|
408
|
+
* - **The first layer's name is reserved.** The bare command, plus a warning: the one-word
|
|
409
|
+
* invocation is gone, the document is not — the no-argument pass still reaches it.
|
|
410
|
+
* - **Otherwise**, the first layer's name. Where several layers share a document, the first is the
|
|
411
|
+
* one the generic-skeleton warning above already names.
|
|
412
|
+
*/
|
|
413
|
+
function analyzeInvocation(layerNames, stubPath, warnings) {
|
|
414
|
+
const target = layerNames[0];
|
|
415
|
+
if (target === undefined)
|
|
416
|
+
return `${ANALYZE_COMMAND} conventions`;
|
|
417
|
+
if (RESERVED_ANALYZE_TARGETS.includes(target)) {
|
|
418
|
+
warnings.push(`the layer \`${target}\` is named for a reserved analyze target, which addresses something else — \`${target}\` means ${reservedMeaning(target)} — so ${stubPath} is reached by \`${ANALYZE_COMMAND}\` with no argument, or by renaming the layer`);
|
|
419
|
+
return ANALYZE_COMMAND;
|
|
420
|
+
}
|
|
421
|
+
return `${ANALYZE_COMMAND} ${target}`;
|
|
422
|
+
}
|
|
423
|
+
/** What each reserved word addresses, for the collision warning above. */
|
|
424
|
+
function reservedMeaning(target) {
|
|
425
|
+
if (target === 'project')
|
|
426
|
+
return 'the always-loaded project file';
|
|
427
|
+
if (target === 'conventions')
|
|
428
|
+
return 'the shared cross-layer document';
|
|
429
|
+
return 'the layer profile itself';
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Enqueue the always-loaded project file, one conventions stub per configured document, and the
|
|
433
|
+
* `*.env.example` companions — every one of them `create-if-absent`.
|
|
434
|
+
*
|
|
435
|
+
* Selection, and why each rule is what it is:
|
|
436
|
+
*
|
|
437
|
+
* - **`CLAUDE.md` always.** It is the file the agent runner loads without being asked, and the only
|
|
438
|
+
* place the read-on-demand rule for everything else is written down.
|
|
439
|
+
* - **The {@link TASK_OFFER_PATH} dialogue always**, because the always-loaded file's fence points
|
|
440
|
+
* at it by name, and an absent target silently disables the offer that pointer describes.
|
|
441
|
+
* - **One stub per distinct `layers[].conventions` path**, plus the shared cross-layer document
|
|
442
|
+
* whether or not a layer names it. A configured path with no document behind it is a pointer an
|
|
443
|
+
* implementer follows to nothing.
|
|
444
|
+
* - **`push-notify.env.example` always**, because the push settings are read whatever the phases
|
|
445
|
+
* are, and **`qa-accounts.env.example` plus the {@link QA_SCENARIOS_PATH} skeleton only when
|
|
446
|
+
* `phases.qa`** — an adopter who never runs the interactive test phase has no accounts to put in
|
|
447
|
+
* the one and no scenarios to write in the other, and an empty credentials example is a file that
|
|
448
|
+
* looks like a step someone forgot to finish.
|
|
449
|
+
*
|
|
450
|
+
* Nothing here writes: the generator plans, and `init` applies the plan once. Its filesystem reads
|
|
451
|
+
* are of three kinds, and each decides a note, a per-request flag or a reported fact rather than any
|
|
452
|
+
* content. Whether the always-loaded file is already there, which decides the kept-file note — an
|
|
453
|
+
* unforced run keeps that file, so the setup-pending banner cannot reach it and the run says so, and
|
|
454
|
+
* whether its `.bak` sibling was already there, which decides
|
|
455
|
+
* {@link ClaudeContextResult.claudeMdBackupExisted}. On
|
|
456
|
+
* a forced run only, that file's own text against the text rendered here, which decides
|
|
457
|
+
* {@link keepsIdenticalProjectFile} and {@link ClaudeContextResult.claudeMdBackupWouldCarryContent}.
|
|
458
|
+
* And, on a forced run only, each conventions document and its `.bak` sibling, which decide
|
|
459
|
+
* {@link keepsRescuedBackup} and the note below it. Those last two kinds are reads rather than a
|
|
460
|
+
* policy change
|
|
461
|
+
* because the answer is a fact about **this** tree at **this** moment: the corpus keeps the
|
|
462
|
+
* `create-if-absent` row it has always had, and a class-wide `--force` exemption like the ledgers'
|
|
463
|
+
* would also stop the forced regeneration of a filled corpus, which is a thing an adopter may
|
|
464
|
+
* legitimately want.
|
|
465
|
+
*/
|
|
466
|
+
export function writeClaudeContext({ repoRoot, config, plan, analyzeOffer = 'accepted', force = false, dryRun = false, }) {
|
|
467
|
+
const warnings = [];
|
|
468
|
+
const notes = [];
|
|
469
|
+
const envExamples = [];
|
|
470
|
+
// Trailing separator removed for the token, which is substituted into a `<root>/…/lessons.md`
|
|
471
|
+
// sentence where a doubled slash reads as a typo; the ledger link is built with `posix.join`,
|
|
472
|
+
// which normalises it either way.
|
|
473
|
+
const stateDir = (config.stateDir ?? DEFAULTS.stateDir).replace(/\/+$/, '');
|
|
474
|
+
const sharedPath = normalizeRepoPathStrict(SHARED_CONVENTIONS_PATH);
|
|
475
|
+
const stubs = collectStubs(config);
|
|
476
|
+
const claudeMdExisted = existsSync(join(repoRoot, CLAUDE_MD_PATH));
|
|
477
|
+
// Read before the plan, like every other fact reported from here: the keep's wording turns on
|
|
478
|
+
// whether a `.bak` was there when the run started, and the write engine may create one below.
|
|
479
|
+
const claudeMdBackupExisted = existsSync(join(repoRoot, `${CLAUDE_MD_PATH}.bak`));
|
|
480
|
+
// Rendered into a local rather than inline, because the identity test below compares the file on
|
|
481
|
+
// disk against this exact text: the answer is what decides the per-request override and the fact
|
|
482
|
+
// `init` words its closing sentence from.
|
|
483
|
+
const claudeMdContent = render('CLAUDE.md', {
|
|
484
|
+
projectName: config.projectName ?? defaultProjectName(repoRoot),
|
|
485
|
+
setupBanner: setupBanner(analyzeOffer),
|
|
486
|
+
routingRows: routingRows(stubs, sharedPath, stateDir),
|
|
487
|
+
stateDir,
|
|
488
|
+
});
|
|
489
|
+
const keepClaudeMd = force && keepsIdenticalProjectFile(repoRoot, claudeMdContent);
|
|
490
|
+
plan.add({
|
|
491
|
+
path: join(repoRoot, CLAUDE_MD_PATH),
|
|
492
|
+
policy: 'create-if-absent',
|
|
493
|
+
...(keepClaudeMd ? { forceOverride: 'never' } : {}),
|
|
494
|
+
content: claudeMdContent,
|
|
495
|
+
label: 'project context file',
|
|
496
|
+
});
|
|
497
|
+
// Planned beside the file that points at it: the two make up one feature, and gating this one on a
|
|
498
|
+
// phase would ship that pointer with no target.
|
|
499
|
+
plan.add({
|
|
500
|
+
path: join(repoRoot, TASK_OFFER_PATH),
|
|
501
|
+
policy: 'create-if-absent',
|
|
502
|
+
content: render('harness-task-offer.md', { pluginName: PLUGIN_NAME }),
|
|
503
|
+
label: 'change-request offer rules',
|
|
504
|
+
});
|
|
505
|
+
if (claudeMdExisted && !force) {
|
|
506
|
+
notes.push(`${CLAUDE_MD_PATH} was already there and ${dryRun ? 'would be kept' : 'was kept'} as it stands, so the setup-pending banner ${dryRun ? 'would not be' : 'was not'} written into it: run \`npx autonomous-sdlc-harness doctor\` at any time for what is still unfilled`);
|
|
507
|
+
}
|
|
508
|
+
const written = [];
|
|
509
|
+
const keptForBackup = [];
|
|
510
|
+
for (const stub of stubs) {
|
|
511
|
+
const layerNames = stub.layers.map((layer) => layer.name);
|
|
512
|
+
if (!stub.dedicated && stub.layers.length > 1) {
|
|
513
|
+
warnings.push(`layers ${joinPhrases(layerNames)} all point at ${stub.path}, which has no dedicated skeleton, so the generic layer stub was written once for ${layerNames[0]}: give each layer its own conventions document, or the rules of one are what every implementer of the others reads`);
|
|
514
|
+
}
|
|
515
|
+
const keep = force && keepsRescuedBackup(repoRoot, stub.path);
|
|
516
|
+
if (keep)
|
|
517
|
+
keptForBackup.push(stub.path);
|
|
518
|
+
plan.add({
|
|
519
|
+
path: join(repoRoot, stub.path),
|
|
520
|
+
policy: 'create-if-absent',
|
|
521
|
+
// The one per-request exception to the run's `--force`, and it is computed from the tree
|
|
522
|
+
// rather than fixed by this artifact's class: the policy, content and label are what they
|
|
523
|
+
// have always been, and an ordinary forced run still replaces the document after a `.bak`.
|
|
524
|
+
...(keep ? { forceOverride: 'never' } : {}),
|
|
525
|
+
content: render(stub.template, {
|
|
526
|
+
layerName: layerNames[0] ?? 'general',
|
|
527
|
+
analyzeInvocation: analyzeInvocation(layerNames, stub.path, warnings),
|
|
528
|
+
}),
|
|
529
|
+
label: `conventions stub ${stub.path}`,
|
|
530
|
+
});
|
|
531
|
+
written.push({ path: stub.path, dedicated: stub.dedicated, layers: layerNames });
|
|
532
|
+
}
|
|
533
|
+
if (keptForBackup.length > 0) {
|
|
534
|
+
// `init`'s `analyzeRecordSentence` needs no edit to stay truthful beside this note, and this is
|
|
535
|
+
// why: its `putBack` clause is keyed on `filledConventions` — the documents that were **not**
|
|
536
|
+
// untouched skeletons when the run started — and a document kept here is by construction one
|
|
537
|
+
// that was. So a kept document never appears in that list, and the run cannot claim to have put
|
|
538
|
+
// back a document it left alone. On a mixed corpus the two lines are complementary: `putBack`
|
|
539
|
+
// names what this run skeletonised, this note names what it kept and why.
|
|
540
|
+
const one = keptForBackup.length === 1;
|
|
541
|
+
notes.push(`${joinPhrases(keptForBackup)} ${dryRun ? 'would be kept' : one ? 'was kept' : 'were kept'} as ${one ? 'it stands' : 'they stand'} under --force: ${one ? 'it is' : 'they are'} already ${one ? 'a skeleton' : 'skeletons'}, and ${one ? 'its' : 'their'} .bak ${one ? 'sibling holds' : 'siblings hold'} the filled analysis an earlier forced run rescued — the engine's .bak is single-generation, so regenerating ${one ? 'it' : 'them'} would overwrite that analysis with a skeleton and leave no copy of it: restore ${one ? `${keptForBackup[0]}.bak` : 'those .bak files'} to get the analysis back, or run \`${ANALYZE_COMMAND}\` again to rebuild it`);
|
|
542
|
+
}
|
|
543
|
+
const pushExample = `${normalizeRepoPathStrict(config.pushEnvPath ?? PUSH_ENV_PATH)}${EXAMPLE_SUFFIX}`;
|
|
544
|
+
plan.add({
|
|
545
|
+
path: join(repoRoot, pushExample),
|
|
546
|
+
policy: 'create-if-absent',
|
|
547
|
+
content: render('push-notify.env.example', {
|
|
548
|
+
pushEnvPath: normalizeRepoPathStrict(config.pushEnvPath ?? PUSH_ENV_PATH),
|
|
549
|
+
}),
|
|
550
|
+
label: 'push-notification settings example',
|
|
551
|
+
});
|
|
552
|
+
envExamples.push(pushExample);
|
|
553
|
+
if (config.phases?.qa === true) {
|
|
554
|
+
const credentialsPath = normalizeRepoPathStrict(config.qa?.credentialsPath ?? QA_CREDENTIALS_PATH);
|
|
555
|
+
const qaExample = `${credentialsPath}${EXAMPLE_SUFFIX}`;
|
|
556
|
+
plan.add({
|
|
557
|
+
path: join(repoRoot, qaExample),
|
|
558
|
+
policy: 'create-if-absent',
|
|
559
|
+
content: render('qa-accounts.env.example', { credentialsPath }),
|
|
560
|
+
label: 'test-account credentials example',
|
|
561
|
+
});
|
|
562
|
+
envExamples.push(qaExample);
|
|
563
|
+
plan.add({
|
|
564
|
+
path: join(repoRoot, QA_SCENARIOS_PATH),
|
|
565
|
+
policy: 'create-if-absent',
|
|
566
|
+
content: render('qa_test_scenarios.md', { credentialsPath }),
|
|
567
|
+
label: 'QA test-scenario rules',
|
|
568
|
+
});
|
|
569
|
+
notes.push(`${QA_SCENARIOS_PATH} is a skeleton for this project's own test-scenario rules, and yours to edit from here on: fill in its banner sections, keep account-specific values in ${credentialsPath}, and a re-run will leave your copy alone`);
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
notes.push('no test-account example and no test-scenario rules were written, because phases.qa is off and both belong to the interactive test phase: turn the phase on in harness.config.json and re-run init to add them');
|
|
573
|
+
}
|
|
574
|
+
const generic = written.filter((stub) => !stub.dedicated);
|
|
575
|
+
if (generic.length > 0) {
|
|
576
|
+
notes.push(`${generic.length} ${generic.length === 1 ? 'layer has' : 'layers have'} no dedicated skeleton, so the generic layer stub was written for ${joinPhrases(generic.map((stub) => stub.path))}`);
|
|
577
|
+
}
|
|
578
|
+
return {
|
|
579
|
+
claudeMd: CLAUDE_MD_PATH,
|
|
580
|
+
claudeMdBackupWouldCarryContent: claudeMdExisted && force && !keepClaudeMd,
|
|
581
|
+
claudeMdBackupExisted: keepClaudeMd && claudeMdBackupExisted,
|
|
582
|
+
stubs: written,
|
|
583
|
+
envExamples,
|
|
584
|
+
warnings,
|
|
585
|
+
notes,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
//# sourceMappingURL=claudeContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claudeContext.js","sourceRoot":"","sources":["../../src/generators/claudeContext.ts"],"names":[],"mappings":"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"}
|