code-foundry 1.36.4 → 1.37.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.
@@ -12,6 +12,10 @@ Quick links: [Code of Conduct](./CODE_OF_CONDUCT.md) · [Security Policy](./SECU
12
12
 
13
13
  [Agent contract](#agent-operating-contract) · [Branches](#branching-model) · [Setup](#before-you-start) · [Validation](#local-validation) · [Internal](#internal-contribution-workflow) · [External](#external-contribution-workflow) · [Pull requests](#pull-request-standards) · [Reviews](#review-and-merge-protocol) · [Security](#security-and-emergencies)
14
14
 
15
+ <!-- /code-foundry-managed: config-aware-policy -->
16
+
17
+ <!-- code-foundry-managed: agent-operating-contract -->
18
+
15
19
  ## Agent operating contract
16
20
 
17
21
  Agents must follow these rules before changing code:
@@ -32,6 +36,8 @@ Agents must not:
32
36
  - Change branch protections, secrets, deployments, or external systems unless that action is explicitly in scope.
33
37
  - Claim completion when tests, deployment checks, or required reviews are still pending.
34
38
 
39
+ <!-- /code-foundry-managed: agent-operating-contract -->
40
+
35
41
  <!-- code-foundry-managed: pull-request-policy -->
36
42
 
37
43
  ### Pull request readiness (mandatory)
@@ -57,6 +63,8 @@ This repository uses the `direct` workflow. Topic pull requests target `main`.
57
63
 
58
64
  <!-- /code-foundry-managed: pull-request-policy -->
59
65
 
66
+ <!-- code-foundry-managed: branching-model -->
67
+
60
68
  ## Branching model
61
69
 
62
70
  ```text
@@ -75,6 +83,10 @@ docs/* test/* refactor/* │
75
83
 
76
84
  The Git workflow is `direct`: topic branches **squash** directly into `main`, and the Release Please version PR **squashes** into `main` (`release_merge_strategy: squash`). Release automation never defaults to a merge method and never merges with `--admin`; `code-foundry doctor` and `code-foundry sync` fail closed on any other release merge strategy. This repository has one protected integration and release branch: `main`.
77
85
 
86
+ <!-- /code-foundry-managed: branching-model -->
87
+
88
+ <!-- code-foundry-managed: before-you-start -->
89
+
78
90
  ## Before you start
79
91
 
80
92
  ### Toolchain
@@ -97,6 +109,10 @@ git switch -c feat/short-description
97
109
 
98
110
  If the worktree is dirty, stop and understand the existing changes before switching branches or editing overlapping files.
99
111
 
112
+ <!-- /code-foundry-managed: before-you-start -->
113
+
114
+ <!-- code-foundry-managed: local-validation -->
115
+
100
116
  ## Local validation
101
117
 
102
118
  The repository runtime detects supported tools and skips checks that do not apply:
@@ -112,6 +128,10 @@ Security and dependency audits run through the GitHub Security workflow.
112
128
 
113
129
  Run the checks relevant to the change. For a release or security-sensitive change, run the complete set. Record the commands and results in the pull request.
114
130
 
131
+ <!-- /code-foundry-managed: local-validation -->
132
+
133
+ <!-- code-foundry-managed: internal-contribution-workflow -->
134
+
115
135
  ## Internal contribution workflow
116
136
 
117
137
  For maintainers, trusted contributors, and automation agents:
@@ -134,6 +154,10 @@ For maintainers, trusted contributors, and automation agents:
134
154
  9. Address review feedback and failed checks on the same branch.
135
155
  10. Merge with a squash after required checks pass and the change is ready; feature PRs land on `main` with squash merges.
136
156
 
157
+ <!-- /code-foundry-managed: internal-contribution-workflow -->
158
+
159
+ <!-- code-foundry-managed: internal-agent-handoff -->
160
+
137
161
  ### Internal agent handoff
138
162
 
139
163
  Every agent handoff should state:
@@ -147,6 +171,10 @@ Risks: known limitations or follow-up work
147
171
  Branch/PR: branch name and pull request link
148
172
  ```
149
173
 
174
+ <!-- /code-foundry-managed: internal-agent-handoff -->
175
+
176
+ <!-- code-foundry-managed: external-contribution-workflow -->
177
+
150
178
  ## External contribution workflow
151
179
 
152
180
  For contributors who do not have direct write access:
@@ -163,6 +191,10 @@ For contributors who do not have direct write access:
163
191
 
164
192
  External contributors should never need repository secrets or production access to validate a normal change.
165
193
 
194
+ <!-- /code-foundry-managed: external-contribution-workflow -->
195
+
196
+ <!-- code-foundry-managed: pull-request-standards -->
197
+
166
198
  ## Pull request standards
167
199
 
168
200
  Every pull request should make these questions easy to answer:
@@ -176,6 +208,10 @@ Every pull request should make these questions easy to answer:
176
208
 
177
209
  Keep pull requests focused and reviewable. Include screenshots or recordings for user-facing changes. Link related issues and use `Closes #123` when appropriate. Complete the [pull request template](./PULL_REQUEST_TEMPLATE.md).
178
210
 
211
+ <!-- /code-foundry-managed: pull-request-standards -->
212
+
213
+ <!-- code-foundry-managed: workflow-and-check-behavior -->
214
+
179
215
  ## Workflow and check behavior
180
216
 
181
217
  | Event | Expected automation |
@@ -199,6 +235,10 @@ Use Conventional Commits so the release automation can determine the next versio
199
235
 
200
236
  Security checks can be skipped when repository visibility or the GitHub plan does not support a feature. A skipped optional check must not be configured as a required status check.
201
237
 
238
+ <!-- /code-foundry-managed: workflow-and-check-behavior -->
239
+
240
+ <!-- code-foundry-managed: review-and-merge-protocol -->
241
+
202
242
  ## Review and merge protocol
203
243
 
204
244
  | Change | Target | Merge method | Merge gate |
@@ -208,6 +248,10 @@ Security checks can be skipped when repository visibility or the GitHub plan doe
208
248
 
209
249
  Reviewers focus on correctness, security, maintainability, test coverage, operational impact, and compatibility. Authors remain responsible for responding to feedback and verifying the final commit.
210
250
 
251
+ <!-- /code-foundry-managed: review-and-merge-protocol -->
252
+
253
+ <!-- code-foundry-managed: security-and-emergencies -->
254
+
211
255
  ## Security and emergencies
212
256
 
213
257
  Report vulnerabilities privately using [SECURITY.md](./SECURITY.md), never in a public issue or pull request.
@@ -221,3 +265,5 @@ For an urgent production or security issue:
221
265
  5. Record follow-up work, remediation, and rollback information.
222
266
 
223
267
  CI bypasses are for documented infrastructure emergencies only and require a follow-up fix. Never use a bypass to hide a code or test failure.
268
+
269
+ <!-- /code-foundry-managed: security-and-emergencies -->
@@ -11,6 +11,10 @@ The latest commit on `main` receives security patches.
11
11
  | `main` | ✅ |
12
12
  | Feature branches | ❌ |
13
13
 
14
+ <!-- /code-foundry-managed: config-aware-policy -->
15
+
16
+ <!-- code-foundry-managed: reporting-a-vulnerability -->
17
+
14
18
  ## Reporting a Vulnerability
15
19
 
16
20
  Do not open a public GitHub issue for a security vulnerability. Report it privately through the repository's Security tab, GitHub security advisory flow, or the private contact method listed on the repository page.
@@ -19,6 +23,12 @@ Include the affected version or commit, impact, reproduction steps, relevant log
19
23
 
20
24
  Maintainers will acknowledge receipt, provide an assessment timeline, coordinate a fix, and agree with the reporter on responsible disclosure. Public disclosure should occur only after a fix or mitigation is available.
21
25
 
26
+ <!-- /code-foundry-managed: reporting-a-vulnerability -->
27
+
28
+ <!-- code-foundry-managed: scope -->
29
+
22
30
  ## Scope
23
31
 
24
32
  This policy covers the code, configuration, dependencies, workflows, and generated artifacts maintained in this repository. Review `Security / Dependency Audit` results for supported ecosystems. Never commit credentials, tokens, private keys, or sensitive environment files. Report accidental secret exposure privately and rotate the credential immediately.
33
+
34
+ <!-- /code-foundry-managed: scope -->
package/AGENTS.md CHANGED
@@ -6,6 +6,10 @@ These instructions are the repository-level operating contract for coding agents
6
6
 
7
7
  They complement `CONTRIBUTING.md`. More specific instructions in nested `AGENTS.md` files and project documentation take precedence for their directory.
8
8
 
9
+ <!-- /code-foundry-managed: config-aware-policy -->
10
+
11
+ <!-- code-foundry-managed: mission -->
12
+
9
13
  ## Mission
10
14
 
11
15
  - Keep formatting, linting, type checking, builds, tests, and coverage reproducible locally and in CI.
@@ -17,6 +21,10 @@ They complement `CONTRIBUTING.md`. More specific instructions in nested `AGENTS.
17
21
 
18
22
  This repository may contain TypeScript, Rust, Python, or any combination of them. Detect the active stack from the files present; do not assume every check applies.
19
23
 
24
+ <!-- /code-foundry-managed: mission -->
25
+
26
+ <!-- code-foundry-managed: contributing-back -->
27
+
20
28
  ## Contributing back
21
29
 
22
30
  Consumers are encouraged to help improve this open-source project. Open a small,
@@ -26,6 +34,10 @@ changes, create an issue first so the proposal can be discussed and scoped.
26
34
  Contributions should help make the tool as performant, reliable, and helpful as
27
35
  possible for everyone.
28
36
 
37
+ <!-- /code-foundry-managed: contributing-back -->
38
+
39
+ <!-- code-foundry-managed: read-before-acting -->
40
+
29
41
  ## Read before acting
30
42
 
31
43
  Before editing:
@@ -45,6 +57,10 @@ Before editing:
45
57
 
46
58
  If the worktree is dirty, preserve existing changes and avoid overlapping edits until their ownership is clear.
47
59
 
60
+ <!-- /code-foundry-managed: read-before-acting -->
61
+
62
+ <!-- code-foundry-managed: priorities -->
63
+
48
64
  ## Priorities
49
65
 
50
66
  When instructions conflict, use this order:
@@ -57,6 +73,10 @@ When instructions conflict, use this order:
57
73
 
58
74
  Ask for clarification when a missing decision would materially change the implementation. Otherwise make the smallest reasonable assumption and document it.
59
75
 
76
+ <!-- /code-foundry-managed: priorities -->
77
+
78
+ <!-- code-foundry-managed: safety-boundaries -->
79
+
60
80
  ## Safety boundaries
61
81
 
62
82
  - Do not discard, reset, overwrite, or rewrite user-owned changes.
@@ -68,6 +88,10 @@ Ask for clarification when a missing decision would materially change the implem
68
88
  - Do not claim completion while required validation, review, deployment, or user decisions remain pending.
69
89
  - Publishing, committing, or opening a pull request requires explicit task scope or user authorization.
70
90
 
91
+ <!-- /code-foundry-managed: safety-boundaries -->
92
+
93
+ <!-- code-foundry-managed: standard-workflow -->
94
+
71
95
  ## Standard workflow
72
96
 
73
97
  1. Restate the desired outcome and identify the files or systems in scope.
@@ -82,6 +106,10 @@ Ask for clarification when a missing decision would materially change the implem
82
106
 
83
107
  For normal feature work, branch from `main` and target pull requests at `main`. Treat `main` as the protected release branch. Follow `.github/CONTRIBUTING.md` for the complete internal and external contribution flow.
84
108
 
109
+ <!-- /code-foundry-managed: standard-workflow -->
110
+
111
+ <!-- code-foundry-managed: git-workflow-and-merging -->
112
+
85
113
  ## Git workflow and merging
86
114
 
87
115
  This repository uses the `direct` workflow: topic branches **squash** directly into `main`, and the Release Please version PR **squashes** into `main` (`release_merge_strategy: squash`). Feature and release PRs land on `main` with squash merges. No integration branch exists; all pull requests target `main`.
@@ -103,6 +131,8 @@ them:
103
131
  - Keep pull requests focused; merge with the canonical method only after
104
132
  required checks pass.
105
133
 
134
+ <!-- /code-foundry-managed: git-workflow-and-merging -->
135
+
106
136
  <!-- code-foundry-managed: pull-request-policy -->
107
137
 
108
138
  ## Code Foundry workflow policy (mandatory)
@@ -130,6 +160,8 @@ This repository uses the `direct` workflow. Topic pull requests target `main`.
130
160
 
131
161
  <!-- /code-foundry-managed: pull-request-policy -->
132
162
 
163
+ <!-- code-foundry-managed: toolchain-and-dependencies -->
164
+
133
165
  ## Toolchain and dependencies
134
166
 
135
167
  - Follow `toolchain: auto` in `.github/code-foundry.yml`; use native tools by
@@ -145,6 +177,10 @@ This repository uses the `direct` workflow. Topic pull requests target `main`.
145
177
  - Do not mix package managers or regenerate lockfiles as a side effect.
146
178
  - Keep dependency additions narrowly scoped and explain security, licensing, and runtime impact.
147
179
 
180
+ <!-- /code-foundry-managed: toolchain-and-dependencies -->
181
+
182
+ <!-- code-foundry-managed: validation -->
183
+
148
184
  ## Validation
149
185
 
150
186
  Use the shared scripts when present. They detect supported tools and skip inapplicable checks:
@@ -175,6 +211,10 @@ At minimum:
175
211
 
176
212
  If a check cannot run, state the exact reason. A skipped check is not a passing check.
177
213
 
214
+ <!-- /code-foundry-managed: validation -->
215
+
216
+ <!-- code-foundry-managed: tests-and-coverage -->
217
+
178
218
  ## Tests and coverage
179
219
 
180
220
  - Add or update tests for behavior changes and regressions.
@@ -183,6 +223,10 @@ If a check cannot run, state the exact reason. A skipped check is not a passing
183
223
  - Keep test data deterministic and remove secrets from logs and fixtures.
184
224
  - Use the narrowest test command while iterating, then run the affected package or workspace suite.
185
225
 
226
+ <!-- /code-foundry-managed: tests-and-coverage -->
227
+
228
+ <!-- code-foundry-managed: github-workflows-and-configuration -->
229
+
186
230
  ## GitHub workflows and configuration
187
231
 
188
232
  - Keep workflows concise, independently runnable, and safe to re-run.
@@ -196,6 +240,10 @@ If a check cannot run, state the exact reason. A skipped check is not a passing
196
240
  - Optional Turborepo Remote Caching uses `TURBO_TOKEN` and `TURBO_TEAM`; do not add Vercel deployment behavior just to enable caching.
197
241
  - Update branch protection when adding or renaming required job checks; verify the actual GitHub status context.
198
242
 
243
+ <!-- /code-foundry-managed: github-workflows-and-configuration -->
244
+
245
+ <!-- code-foundry-managed: documentation-and-generated-files -->
246
+
199
247
  ## Documentation and generated files
200
248
 
201
249
  - Update documentation when behavior, setup, configuration, commands, or operational procedures change.
@@ -203,6 +251,10 @@ If a check cannot run, state the exact reason. A skipped check is not a passing
203
251
  - Do not commit build output, caches, coverage output, dependency directories, generated credentials, or temporary files.
204
252
  - Preserve formatting and line-ending conventions from `.editorconfig` and `.gitattributes`.
205
253
 
254
+ <!-- /code-foundry-managed: documentation-and-generated-files -->
255
+
256
+ <!-- code-foundry-managed: completion-report -->
257
+
206
258
  ## Completion report
207
259
 
208
260
  End every agent task with:
@@ -217,3 +269,5 @@ Branch/PR:
217
269
  ```
218
270
 
219
271
  Use exact command names and outcomes. Mention external changes separately from local changes, and distinguish completed work from recommendations.
272
+
273
+ <!-- /code-foundry-managed: completion-report -->
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.37.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.36.4...v1.37.0) (2026-09-30)
4
+
5
+
6
+ ### Features
7
+
8
+ * preserve repository-owned sections of managed policy documents ([#668](https://github.com/0xPlayerOne/code-foundry/issues/668)) ([26e9f4b](https://github.com/0xPlayerOne/code-foundry/commit/26e9f4bfeaca8b01087626bafbfc5090d891530f)), closes [#667](https://github.com/0xPlayerOne/code-foundry/issues/667)
9
+
3
10
  ## [1.36.4](https://github.com/0xPlayerOne/code-foundry/compare/v1.36.3...v1.36.4) (2026-09-29)
4
11
 
5
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "1.36.4",
3
+ "version": "1.37.0",
4
4
  "description": "A fast, language-aware repository factory for agent-ready workflows, testing, security, and release automation.",
5
5
  "homepage": "https://github.com/0xPlayerOne/code-foundry#readme",
6
6
  "bugs": {
@@ -299,15 +299,21 @@ function synchronize(options) {
299
299
  }
300
300
  if (!force && protectedFiles.has(file) && existsSync(destination)) {
301
301
  const existing = readFileSync(destination, 'utf8')
302
- if (!isLegacyManagedDoc(file, existing) && !isManagedConfigPolicy(file, existing)) {
303
- if (configAwarePolicyFiles.has(file)) {
304
- const merged = mergeManagedPolicyBlocks(existing, content.toString('utf8'))
305
- if (merged !== existing) {
306
- changed.push(file)
307
- writeOrReport(destination, merged, dryRun)
308
- }
302
+ if (isLegacyManagedDoc(file, existing)) {
303
+ // Ancient scaffolds predate managed blocks entirely; fall through to
304
+ // the full overwrite so they migrate to the marked layout.
305
+ } else if (configAwarePolicyFiles.has(file)) {
306
+ // Refresh managed blocks in place and keep every unmarked line. A
307
+ // whole-file overwrite here would silently delete repository-owned
308
+ // sections of the policy documents (issue #667).
309
+ const merged = refreshManagedPolicyDocument(existing, content.toString('utf8'))
310
+ if (merged !== existing) {
311
+ changed.push(file)
312
+ writeOrReport(destination, merged, dryRun)
309
313
  }
310
314
  continue
315
+ } else {
316
+ continue
311
317
  }
312
318
  }
313
319
  if (!existsSync(destination) || !buffersEqual(content, readFileSync(destination))) {
@@ -1375,91 +1381,103 @@ function isLegacyManagedDoc(file, content) {
1375
1381
  return false
1376
1382
  }
1377
1383
 
1384
+ const MANAGED_BLOCK_PATTERN =
1385
+ /<!-- code-foundry-managed: ([A-Za-z0-9_-]+) -->[\s\S]*?<!-- \/code-foundry-managed: \1 -->/g
1386
+ const MANAGED_MARKER_LINE = /^[ \t]*<!-- \/?code-foundry-managed: [A-Za-z0-9_-]+ -->[ \t]*\r?\n/gm
1387
+
1388
+ /** @param {string} id */
1389
+ function managedBlockPattern(id) {
1390
+ const start = `<!-- code-foundry-managed: ${id} -->`
1391
+ const end = `<!-- /code-foundry-managed: ${id} -->`
1392
+ return new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}`, 'g')
1393
+ }
1394
+
1378
1395
  /**
1379
- * Config-aware policy documents are generated contracts: a normal sync must
1380
- * refresh generated copies after branch-topology or validation-policy edits.
1381
- * Unmarked consumer-owned documents receive only the explicitly marked policy
1382
- * blocks, so another agent's initializer cannot be overwritten. The top-level
1383
- * marker owns generated copies. Exact scaffold signatures migrate older
1384
- * generated copies without treating arbitrary repository documentation as
1385
- * managed.
1386
- * @param {string} file
1396
+ * Remove managed marker lines that do not belong to a complete block. The
1397
+ * pre-block templates carried a single unclosed `config-aware-policy` marker
1398
+ * as an ownership flag; that residue (and any hand-mangled leftover marker)
1399
+ * is migration noise, not content, and would otherwise break block matching.
1400
+ * Markers inside complete blocks are kept.
1387
1401
  * @param {string} content
1402
+ * @returns {string}
1388
1403
  */
1389
- function isManagedConfigPolicy(file, content) {
1390
- if (!configAwarePolicyFiles.has(file)) return false
1391
- if (content.includes('<!-- code-foundry-managed: config-aware-policy -->')) return true
1392
- if (file === 'AGENTS.md') {
1393
- return (
1394
- content.startsWith('# Agent Instructions\n') &&
1395
- content.includes(
1396
- 'These instructions are the repository-level operating contract for coding agents'
1397
- ) &&
1398
- content.includes('They complement `CONTRIBUTING.md`.')
1399
- )
1400
- }
1401
- if (file === '.github/CONTRIBUTING.md') {
1402
- return (
1403
- content.startsWith('# Contributing\n') &&
1404
- content.includes(
1405
- 'This guide is the operating contract for humans and automation contributing to this repository.'
1406
- ) &&
1407
- content.includes('[Agent contract](#agent-operating-contract)')
1408
- )
1409
- }
1410
- return (
1411
- content.startsWith('# Security Policy\n') &&
1412
- content.includes('## Reporting a Vulnerability') &&
1413
- content.includes(
1414
- 'This policy covers the code, configuration, dependencies, workflows, and generated artifacts maintained in this repository.'
1415
- )
1416
- )
1417
- }
1418
-
1419
- /** @param {string} target @param {string[]} args */
1420
- function git(target, args) {
1421
- spawnSync('git', args, { cwd: target, stdio: 'ignore' })
1404
+ function stripUnpairedManagedMarkers(content) {
1405
+ const spans = [...content.matchAll(MANAGED_BLOCK_PATTERN)].map((match) => [
1406
+ match.index,
1407
+ match.index + match[0].length,
1408
+ ])
1409
+ if (!spans.length) return content.replace(MANAGED_MARKER_LINE, '')
1410
+ let out = ''
1411
+ let cursor = 0
1412
+ for (const match of content.matchAll(MANAGED_MARKER_LINE)) {
1413
+ const index = match.index
1414
+ if (spans.some(([from, to]) => index >= from && index < to)) continue
1415
+ out += content.slice(cursor, index)
1416
+ cursor = index + match[0].length
1417
+ }
1418
+ return out + content.slice(cursor)
1422
1419
  }
1423
1420
 
1424
1421
  /**
1425
- * Merge the marked policy blocks from a rendered baseline into a consumer-owned
1426
- * policy document. Unmarked AGENTS.md/CONTRIBUTING.md files are commonly
1427
- * generated by another agent initializer, so sync must add and refresh the
1428
- * Code Foundry contract without replacing the user's surrounding instructions.
1422
+ * Refresh the managed policy blocks in a policy document against a rendered
1423
+ * baseline while preserving every unmarked line (issue #667). Blocks the
1424
+ * document already carries are replaced with the baseline content in place,
1425
+ * so config-driven prose (branch topology, validation tiers) always follows
1426
+ * the repository configuration. Missing blocks migrate in place when their
1427
+ * baseline content is present verbatim — documents rendered by the pre-block
1428
+ * templates carried the same prose unmarked — and are appended at the end
1429
+ * otherwise, so hand-edited generated regions and consumer-owned sections
1430
+ * are never silently deleted.
1429
1431
  * @param {string} existing
1430
1432
  * @param {string} baseline
1431
1433
  * @returns {string}
1432
1434
  */
1433
- function mergeManagedPolicyBlocks(existing, baseline) {
1434
- const blockPattern =
1435
- /<!-- code-foundry-managed: ([A-Za-z0-9_-]+) -->[\s\S]*?<!-- \/code-foundry-managed: \1 -->/g
1436
- const blocks = [...baseline.matchAll(blockPattern)]
1435
+ function refreshManagedPolicyDocument(existing, baseline) {
1436
+ const blocks = [...baseline.matchAll(MANAGED_BLOCK_PATTERN)]
1437
1437
  if (!blocks.length) return existing
1438
1438
 
1439
1439
  let merged = existing
1440
+ /** @type {Set<string>} */
1441
+ const present = new Set()
1440
1442
  for (const match of blocks) {
1441
1443
  const id = match[1]
1442
- const block = match[0]
1443
- const start = `<!-- code-foundry-managed: ${id} -->`
1444
- const end = `<!-- /code-foundry-managed: ${id} -->`
1445
- const pattern = new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}`, 'g')
1446
- const occurrences = merged.match(pattern) ?? []
1444
+ const occurrences = [...merged.matchAll(managedBlockPattern(id))]
1447
1445
  if (occurrences.length > 1) {
1448
1446
  throw new Error(`Managed policy block appears more than once in consumer document: ${id}`)
1449
1447
  }
1450
1448
  if (occurrences.length === 1) {
1451
- merged = merged.replace(pattern, block)
1452
- continue
1449
+ merged = merged.replace(managedBlockPattern(id), () => match[0])
1450
+ present.add(id)
1453
1451
  }
1454
- if (merged.includes(start) || merged.includes(end)) {
1455
- throw new Error(`Managed policy block is incomplete in consumer document: ${id}`)
1452
+ }
1453
+
1454
+ merged = stripUnpairedManagedMarkers(merged)
1455
+
1456
+ for (const match of blocks) {
1457
+ if (present.has(match[1])) continue
1458
+ const start = `<!-- code-foundry-managed: ${match[1]} -->`
1459
+ const end = `<!-- /code-foundry-managed: ${match[1]} -->`
1460
+ const inner = match[0]
1461
+ .slice(start.length, match[0].length - end.length)
1462
+ .replace(/^\n+/, '')
1463
+ .replace(/\n+$/, '')
1464
+ if (inner && merged.includes(inner)) {
1465
+ // Wrap the legacy unmarked region in place; the replacer function keeps
1466
+ // `$` sequences in the prose from being treated as substitution patterns.
1467
+ merged = merged.replace(inner, () => match[0])
1468
+ continue
1456
1469
  }
1457
1470
  const separator = merged.endsWith('\n\n') ? '' : merged.endsWith('\n') ? '\n' : '\n\n'
1458
- merged = `${merged}${separator}${block}\n`
1471
+ merged = `${merged}${separator}${match[0]}\n`
1459
1472
  }
1460
1473
  return merged
1461
1474
  }
1462
1475
 
1476
+ /** @param {string} target @param {string[]} args */
1477
+ function git(target, args) {
1478
+ spawnSync('git', args, { cwd: target, stdio: 'ignore' })
1479
+ }
1480
+
1463
1481
  /** @param {string} file @param {Buffer|string} content @param {boolean} dryRun */
1464
1482
  function writeOrReport(file, content, dryRun) {
1465
1483
  if (dryRun) {