@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.34 → 0.1.37

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 (104) hide show
  1. package/bin/adl +2 -2
  2. package/bin/reconcile +4 -3
  3. package/package.json +1 -1
  4. package/packages/agent-hooks/README.md +14 -0
  5. package/packages/agent-hooks/assets/opencode-plugin.template.ts +140 -104
  6. package/packages/agent-hooks/deploy-profile.json +1 -1
  7. package/packages/agent-hooks/src/deploy/opencode-template.ts +5 -18
  8. package/packages/agent-hooks/src/deploy/render.ts +12 -3
  9. package/packages/agent-hooks/src/pre-tool-use/handler.ts +2 -2
  10. package/packages/agent-hooks/src/rules/impl/adl-build-run-role.ts +116 -0
  11. package/packages/agent-hooks/src/rules/impl/adl-git-user-override-block.ts +159 -0
  12. package/packages/agent-hooks/src/rules/impl/adl-mcp-cli.ts +12 -6
  13. package/packages/agent-hooks/src/rules/impl/adl-pr-complete-pending.ts +9 -9
  14. package/packages/agent-hooks/src/rules/impl/adl-pr-role.ts +6 -5
  15. package/packages/agent-hooks/src/rules/impl/agent-tamper-protection.ts +15 -14
  16. package/packages/agent-hooks/src/rules/impl/git-core.ts +4 -8
  17. package/packages/agent-hooks/src/rules/impl/mcp-tool.ts +1 -1
  18. package/packages/agent-hooks/src/rules/impl/tool-native-search.ts +8 -7
  19. package/packages/agent-hooks/src/rules/registry.ts +11 -2
  20. package/packages/agent-hooks/src/rules/shell/protected-carriers.ts +9 -5
  21. package/packages/agent-settings/README.md +10 -4
  22. package/packages/agent-settings/assets/TARUN-GLOBAL-AGENTS-INSTRUCTIONS.md +40 -0
  23. package/packages/agent-settings/src/global-agents.ts +43 -8
  24. package/packages/agent-skills/assets/adl-mechanics/SKILL.md +38 -0
  25. package/packages/agent-skills/assets/agent-search-tool-audit/scripts/audit.sh +1 -1
  26. package/packages/agent-skills/assets/code-review/SKILL.md +84 -66
  27. package/packages/agent-skills/assets/diagnosing-bugs/SKILL.md +23 -2
  28. package/packages/agent-skills/assets/diagnosing-bugs/references/FEEDBACK-LOOP-TACTICS.md +1 -1
  29. package/packages/agent-skills/assets/explore-codebase/SKILL.md +3 -2
  30. package/packages/agent-skills/assets/finalize-feature/SKILL.md +63 -0
  31. package/packages/agent-skills/assets/fix-ci/SKILL.md +58 -0
  32. package/packages/agent-skills/assets/handoff/SKILL.md +1 -1
  33. package/packages/agent-skills/assets/implement-feature/SKILL.md +153 -0
  34. package/packages/agent-skills/assets/implement-feature/references/briefs.md +94 -0
  35. package/packages/agent-skills/assets/implement-item/SKILL.md +71 -0
  36. package/packages/agent-skills/assets/mechanics/SKILL.md +38 -0
  37. package/packages/agent-skills/assets/plan-feature/SKILL.md +141 -0
  38. package/packages/agent-skills/assets/prepare-promotion/SKILL.md +91 -0
  39. package/packages/agent-skills/assets/ralplan/SKILL.md +149 -0
  40. package/packages/agent-skills/assets/refactor-safely/SKILL.md +4 -3
  41. package/packages/agent-skills/assets/report-bug/SKILL.md +72 -0
  42. package/packages/agent-skills/assets/review-change/SKILL.md +82 -0
  43. package/packages/agent-skills/assets/review-plan/SKILL.md +124 -0
  44. package/packages/agent-skills/assets/to-feature/SKILL.md +1 -1
  45. package/packages/agent-skills/assets/to-workitems/SKILL.md +1 -1
  46. package/packages/agent-skills/assets/to-workitems/references/AZURE-DEVOPS-HIERARCHY.md +3 -0
  47. package/packages/agent-skills/assets/triage-workitems/SKILL.md +99 -0
  48. package/packages/agent-skills/assets/ultragoal/SKILL.md +77 -0
  49. package/packages/agent-skills/assets/validate-design/SKILL.md +70 -0
  50. package/packages/agent-skills/assets/verify-item/SKILL.md +71 -0
  51. package/packages/agent-skills/assets/workitems-process/SKILL.md +147 -0
  52. package/packages/agent-skills/assets/workitems-process/references/ado-and-git.md +194 -0
  53. package/packages/agent-skills/assets/workitems-process/references/evidence.md +74 -0
  54. package/packages/agent-skills/assets/workitems-process/references/workitem-states.md +92 -0
  55. package/packages/agent-skills/assets/write-feature/SKILL.md +95 -0
  56. package/packages/agent-skills/dist/deploy.d.ts +7 -0
  57. package/packages/agent-skills/dist/deploy.d.ts.map +1 -1
  58. package/packages/agent-skills/dist/deploy.js +52 -5
  59. package/packages/agent-skills/dist/deploy.js.map +1 -1
  60. package/packages/agent-skills/src/deploy.ts +48 -4
  61. package/packages/azdo/scripts/probe-ado-rules.ts +1 -1
  62. package/packages/azdo/src/activity-log.ts +118 -0
  63. package/packages/azdo/src/azdo-attachments.ts +108 -0
  64. package/packages/azdo/src/azdo-pl.ts +100 -3
  65. package/packages/azdo/src/azdo-pr.ts +194 -11
  66. package/packages/azdo/src/azdo-wit.ts +329 -18
  67. package/packages/azdo/src/cli.ts +42 -2
  68. package/packages/azdo/src/lib/agent-identity.ts +1 -1
  69. package/packages/azdo/src/lib/client.ts +7 -1
  70. package/packages/azdo/src/lib/validate-pr-metadata.ts +14 -5
  71. package/packages/azdo/src/mcp-server.ts +53 -5
  72. package/packages/azdo/src/tool-dispatch.ts +53 -4
  73. package/packages/azdo/src/workflow-ladder.csv +125 -107
  74. package/packages/azdo/src/workflow-rules.ts +254 -97
  75. package/packages/repo-controls/README.md +24 -11
  76. package/packages/repo-controls/assets/scaffold/docs/AGENTS.md +5 -5
  77. package/packages/repo-controls/assets/scaffold/plans/AGENTS.md +42 -13
  78. package/packages/repo-controls/assets/scaffold/root/AGENTS.md +60 -11
  79. package/packages/repo-controls/assets/scaffold/root/CONTRIBUTING.md +28 -0
  80. package/packages/repo-controls/assets/scaffold/root/INVARIANTS.md +17 -0
  81. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/test_check.py +0 -11
  82. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/filename/test_check.py +4 -6
  83. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/ledger/test_check.py +15 -0
  84. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/docs-okf-profiles.yaml +1 -17
  85. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/test_check.py +3 -22
  86. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/check.py +39 -182
  87. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/fixtures/open-work-cap-clean/plans/features/az601-clean/intent.md +17 -0
  88. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/test_check.py +90 -321
  89. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plan_metadata.py +31 -5
  90. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/check.py +282 -54
  91. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/design.md +35 -0
  92. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/file-map-reference.md +19 -0
  93. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/intent.md +25 -0
  94. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/items/az351-restore-entrypoint.md +25 -0
  95. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/requirements.md +44 -0
  96. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-violation/plans/features/az347-test-coverage/requirements.md +44 -0
  97. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/test_check.py +646 -53
  98. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/cli.py +3 -3
  99. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/docs_shape.py +1 -12
  100. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.alt.yaml +1 -1
  101. package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/gate-registry.yaml +1 -1
  102. package/packages/repo-controls/src/cli.ts +23 -5
  103. package/packages/repo-controls/src/constants.ts +2 -15
  104. package/packages/repo-controls/src/install.ts +15 -6
@@ -389,9 +389,9 @@ def main(argv: Sequence[str] | None = None) -> int:
389
389
  "`root-files=STATUS.md,DRIFT.md` or `root-dirs=apps,packaging`; and "
390
390
  "`scripts-direct-files` (LAY-014), a positive integer replacing the "
391
391
  "default limit of 15 direct files per scripts directory, as "
392
- "`scripts-direct-files=20`; `open-gaps` and `open-plans` (LAY-016), "
393
- "each a positive integer replacing the default cap of 10 open gaps "
394
- "or open plans, as `open-gaps=37` and `open-plans=11`; and "
392
+ "`scripts-direct-files=20`; `open-plans` (LAY-016), "
393
+ "a positive integer replacing the default cap of 10 open plans, "
394
+ "as `open-plans=11`; and "
395
395
  "`markdown-roots` (LAY-006), a "
396
396
  "comma-separated list of repository-relative directories whose "
397
397
  "Markdown is allowed in addition to the declared docs trees "
@@ -6,8 +6,7 @@ this", and the answer is declared once, here, so a second docs gate does not
6
6
  grow its own, independently drifting copy the way LAY-008/LAY-012 once did for
7
7
  the repository root (see `root_shape.py`'s module docstring).
8
8
 
9
- Every entry below is read off an EXISTING docs gate, except `notes.md`, which
10
- is declared by an explicit decision (see its entry):
9
+ Every entry below is read off an EXISTING docs gate:
11
10
 
12
11
  ALLOWED_DOCS_DIRS
13
12
  architecture, research, compliance, operations
@@ -43,15 +42,6 @@ ALLOWED_DOCS_FILES
43
42
  no repository in this estate ships a top-level `docs/index.md` today,
44
43
  and declaring it costs nothing.
45
44
 
46
- notes.md
47
- the task ledger. Not read off a docs gate: Tarun's global agent rule
48
- (decision of 2026-10-01) requires every agent to append one line to
49
- `docs/notes.md` when it starts a task and one when it finishes
50
- (append-only, at most 100 characters per line), so the file exists in
51
- every repository an agent works in and this gate would otherwise flag
52
- it as undeclared. `docs-okf-profiles.yaml` carries a matching
53
- `docs/notes.md` profile so `docs.okf-profile` (DOC-006) grades it too.
54
-
55
45
  These are PERMITTED, not required — the same distinction `root_shape.py`
56
46
  draws for the repository root. A repository using only two of the five
57
47
  directories is not missing three findings; it has simply not written those
@@ -72,5 +62,4 @@ ALLOWED_DOCS_FILES: frozenset[str] = frozenset({
72
62
  "AGENTS.md",
73
63
  "CLAUDE.md",
74
64
  "index.md",
75
- "notes.md",
76
65
  })
@@ -605,7 +605,7 @@ rows:
605
605
  - id: LAY-016
606
606
  name: open-work-cap
607
607
  domain: layout
608
- title: Cap open plans and open gaps at ten
608
+ title: Cap open plans at ten
609
609
  severity: M
610
610
  controls:
611
611
  - EC-A.5.36
@@ -605,7 +605,7 @@ rows:
605
605
  - id: LAY-016
606
606
  name: open-work-cap
607
607
  domain: layout
608
- title: Cap open plans and open gaps at ten
608
+ title: Cap open plans at ten
609
609
  severity: M
610
610
  controls:
611
611
  - EC-A.5.36
@@ -1,4 +1,4 @@
1
- import { HOOK_NAMES, TOOL, VERSION } from './constants.ts'
1
+ import { HOOK_NAMES, SCAFFOLD_ROOT_FILES, TOOL, VERSION } from './constants.ts'
2
2
  import { doctor } from './doctor.ts'
3
3
  import { azdoPush } from './azdo-push.ts'
4
4
  import { ensurePromotionPullRequest } from './promote.ts'
@@ -18,7 +18,8 @@ export interface Io {
18
18
  }
19
19
 
20
20
  const USAGE = `usage:
21
- ${TOOL} install [--repo <path>] [--dry-run] [--json] [--no-grade] [--rewire]
21
+ ${TOOL} install [--repo <path>] [--dry-run] [--json] [--no-grade] [--rewire] [--force-templates[=<file,...>]]
22
+ (--force-templates: overwrite the root templates; bare = all of ${SCAFFOLD_ROOT_FILES.join(', ')}, or a comma list)
22
23
  ${TOOL} grade [--repo <path>] (prints the compliance verdict as JSON; exit 0 whatever the verdict)
23
24
  ${TOOL} uninstall [--repo <path>] [--dry-run]
24
25
  ${TOOL} doctor [--repo <path>] [--json]
@@ -40,6 +41,8 @@ interface Parsed {
40
41
  flags: Set<string>
41
42
  positional: string[]
42
43
  rest: string[]
44
+ /** The raw `--force-templates=<list>` value; undefined for the bare flag. */
45
+ forced?: string
43
46
  }
44
47
 
45
48
  function parse(command: string, argv: string[], allowed: string[]): Parsed {
@@ -50,7 +53,11 @@ function parse(command: string, argv: string[], allowed: string[]): Parsed {
50
53
  parsed.rest = argv.slice(index + 1)
51
54
  break
52
55
  }
53
- if (arg === '--repo') {
56
+ if (arg === '--force-templates' || arg.startsWith('--force-templates=')) {
57
+ if (!allowed.includes('--force-templates')) throw new UsageError(`unknown option for ${command}: ${arg}`)
58
+ parsed.flags.add('--force-templates')
59
+ if (arg.includes('=')) parsed.forced = arg.slice(arg.indexOf('=') + 1)
60
+ } else if (arg === '--repo') {
54
61
  parsed.repo = argv[++index] ?? ''
55
62
  } else if (arg.startsWith('--')) {
56
63
  if (!allowed.includes(arg)) throw new UsageError(`unknown option for ${command}: ${arg}`)
@@ -66,6 +73,15 @@ function parse(command: string, argv: string[], allowed: string[]): Parsed {
66
73
  return parsed
67
74
  }
68
75
 
76
+ /** The root templates named by `--force-templates[=<list>]`; the bare flag means all, an unknown or empty name is refused. */
77
+ function forcedTemplates(value: string | undefined): string[] {
78
+ if (value === undefined) return [...SCAFFOLD_ROOT_FILES]
79
+ const names = value.split(',')
80
+ const unknown = names.filter((name) => !(SCAFFOLD_ROOT_FILES as readonly string[]).includes(name))
81
+ if (unknown.length > 0) throw new UsageError(`unknown root template for --force-templates: ${unknown.map((n) => `'${n}'`).join(', ')} (valid: ${SCAFFOLD_ROOT_FILES.join(', ')})`)
82
+ return names
83
+ }
84
+
69
85
  function printActions(io: Io, actions: Action[], dryRun: boolean): void {
70
86
  const tag = dryRun ? '[dry-run] ' : ''
71
87
  for (const action of actions) io.out(`${tag}${action.kind} ${action.target} (${action.reason})`)
@@ -84,10 +100,12 @@ async function dispatch(argv: string[], io: Io): Promise<number> {
84
100
  io.out(USAGE)
85
101
  return 0
86
102
  case 'install': {
87
- const parsed = parse(command, args, ['--dry-run', '--json', '--no-grade', '--rewire'])
103
+ const parsed = parse(command, args, ['--dry-run', '--json', '--no-grade', '--rewire', '--config-created', '--force-templates'])
88
104
  const rewire = parsed.flags.has('--rewire')
105
+ const forceTemplates = parsed.flags.has('--force-templates') ? forcedTemplates(parsed.forced) : []
106
+ if (rewire && forceTemplates.length > 0) throw new UsageError('--force-templates cannot be combined with --rewire (a re-wire never writes root templates)')
89
107
  const grade = parsed.flags.has('--no-grade') || rewire ? {} : { grade: { env: io.env } }
90
- const result = install(parsed.repo, { dryRun: parsed.flags.has('--dry-run'), rewire, ...grade })
108
+ const result = install(parsed.repo, { dryRun: parsed.flags.has('--dry-run'), rewire, configCreated: parsed.flags.has('--config-created'), forceTemplates, ...grade })
91
109
  if (parsed.flags.has('--json')) {
92
110
  io.out(JSON.stringify(result))
93
111
  return 0
@@ -2,16 +2,6 @@ import { readFileSync } from 'node:fs'
2
2
  import { join, resolve } from 'node:path'
3
3
 
4
4
  export const TOOL = 'adl-repo-controls'
5
- /**
6
- * Never published (2026-09-27): kept only as this
7
- * package's own internal identity (USAGE text, error messages, the lock
8
- * file's `tool` field) — none of that is a claim that `adl-repo-controls`
9
- * is installable. NPM_INSTALL/PACKAGE_NAME describe a registry package that
10
- * does not exist; do not point a user-facing "how do I get this on PATH"
11
- * message at them (see ADL_NPM_INSTALL below for the one that is real).
12
- */
13
- const PACKAGE_NAME = '@amsterdamdatalabs/adl-repo-controls'
14
- const NPM_INSTALL = `npm i -g ${PACKAGE_NAME}`
15
5
  /**
16
6
  * How a repository's committed git-hook stubs actually get `adl` onto
17
7
  * PATH: this package is folded into the `adl` binary's `repo controls`
@@ -72,11 +62,8 @@ export const RECONCILE_GUARD_REL = '.workflows/reconcile-guard.sh'
72
62
  /** The folder skeleton `install` bootstraps for the adl standard trees; see README, "Scaffolding". */
73
63
  export const SCAFFOLD_ROOT = join(PACKAGE_ROOT, 'assets', 'scaffold')
74
64
  export const SCAFFOLD_TREES = ['docs', 'plans', 'workflows'] as const
75
- /** Repository-root entry files, scaffolded from `assets/scaffold/root/` when absent (never overwritten). */
76
- export const SCAFFOLD_ROOT_FILES = ['AGENTS.md', 'CLAUDE.md'] as const
77
- /** The invariants that apply to ALL repositories; install deploys it as `INVARIANTS.md` only when the target has none. */
78
- export const GLOBAL_INVARIANTS_SOURCE = join(PACKAGE_ROOT, 'assets', 'GLOBAL-INVARIANTS.md')
79
- export const GLOBAL_INVARIANTS_TARGET = 'INVARIANTS.md'
65
+ /** Repository-root templates in `assets/scaffold/root/`: written only by a run that creates adl-config.toml, overwritten only by `--force-templates`. */
66
+ export const SCAFFOLD_ROOT_FILES = ['AGENTS.md', 'CLAUDE.md', 'CONTRIBUTING.md', 'INVARIANTS.md'] as const
80
67
 
81
68
  export const MANAGED_ASSETS = [
82
69
  { rel: PROMOTE_REL, source: join(PACKAGE_ROOT, 'assets', 'promote.ts'), mode: 0o755, doctorId: 'promote' },
@@ -24,8 +24,6 @@ import {
24
24
  ADL_NPM_INSTALL,
25
25
  CONFIG_REL,
26
26
  GITIGNORE_REL,
27
- GLOBAL_INVARIANTS_SOURCE,
28
- GLOBAL_INVARIANTS_TARGET,
29
27
  HOOK_NAMES,
30
28
  HOOKS_PATH,
31
29
  LOCK_REL,
@@ -234,6 +232,10 @@ export interface InstallOptions {
234
232
  grade?: { env: Env }
235
233
  /** Re-wire only: no config seeding, no scaffolding, no grading (fleet reconcile; see README, "Re-wiring"). */
236
234
  rewire?: boolean
235
+ /** This run created adl-config.toml, so the root templates are written when absent; see README, "Root templates". */
236
+ configCreated?: boolean
237
+ /** Root templates to overwrite (`--force-templates`); the only way an existing template is replaced. */
238
+ forceTemplates?: readonly string[]
237
239
  }
238
240
 
239
241
  export interface InstallResult {
@@ -266,6 +268,7 @@ function readCentralMarker(repo: string, dryRun: boolean): string {
266
268
  export function install(repoArg: string, options: InstallOptions): InstallResult {
267
269
  const repo = resolveRepo(repoArg)
268
270
  const dryRun = options.dryRun === true
271
+ const configMissing = !existsSync(join(repo, CONFIG_REL))
269
272
  const initialConfigText = readCentralMarker(repo, dryRun)
270
273
  // Validate only repo-controls' declared tables before deleting its root.
271
274
  // Other owners' tables remain deliberately opaque.
@@ -336,6 +339,7 @@ export function install(repoArg: string, options: InstallOptions): InstallResult
336
339
  created: complianceResult.created,
337
340
  sha256: sha256(extractTableSpan(finalConfigText, 'controls.compliance') as string),
338
341
  }
342
+ scaffoldRootTemplates(ops, options.configCreated === true || (dryRun && configMissing), options.forceTemplates ?? [])
339
343
  scaffoldStandardTrees(finalConfigText, ops)
340
344
  }
341
345
 
@@ -507,10 +511,6 @@ function assertComplianceTree(value: unknown): void {
507
511
  /** Bootstrap the standard trees at their declared paths, only where the folder is missing; see README, "Scaffolding". */
508
512
  function scaffoldStandardTrees(configText: string, ops: Ops): void {
509
513
  const missing = declaredScaffoldTrees(configText).filter(({ base }) => !ops.occupied(base))
510
- for (const rel of SCAFFOLD_ROOT_FILES) {
511
- ops.create(rel, readFileSync(join(SCAFFOLD_ROOT, 'root', rel)), 'scaffold the repository entry files (user-owned after install)')
512
- }
513
- ops.create(GLOBAL_INVARIANTS_TARGET, readFileSync(GLOBAL_INVARIANTS_SOURCE), 'deploy the global invariants (user-owned after install)')
514
514
  for (const { name, base } of missing) {
515
515
  for (const rel of scaffoldFiles(join(SCAFFOLD_ROOT, name), '')) {
516
516
  ops.create(`${base}/${rel}`, readFileSync(join(SCAFFOLD_ROOT, name, rel)), `scaffold the ${name} tree (user-owned after install)`)
@@ -518,6 +518,15 @@ function scaffoldStandardTrees(configText: string, ops: Ops): void {
518
518
  }
519
519
  }
520
520
 
521
+ /** Root templates: created when absent on a run that created adl-config.toml, overwritten only for the forced names; see README, "Root templates". */
522
+ function scaffoldRootTemplates(ops: Ops, created: boolean, forced: readonly string[]): void {
523
+ for (const rel of SCAFFOLD_ROOT_FILES) {
524
+ const bytes = readFileSync(join(SCAFFOLD_ROOT, 'root', rel))
525
+ if (forced.includes(rel)) ops.write(rel, bytes, 'overwrite the root template (--force-templates)')
526
+ else if (created) ops.create(rel, bytes, 'scaffold the repository root template (user-owned after install)')
527
+ }
528
+ }
529
+
521
530
  /** Every file under `dir`, relative to it, sorted, depth-first. */
522
531
  function scaffoldFiles(dir: string, prefix: string): string[] {
523
532
  return readdirSync(join(dir, prefix), { withFileTypes: true })