@starci/hfs 4.7.47 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +4 -9
  3. package/emit/operation-schemas.mjs +3 -0
  4. package/lint/bound-sys.cjs +2 -2
  5. package/package.json +11 -5
  6. package/runtime/engine/admission.mjs +1 -1
  7. package/runtime/engine/config.mjs +2 -2
  8. package/runtime/engine/db/machine.mjs +2 -3
  9. package/runtime/engine/invalid-config.mjs +1 -1
  10. package/runtime/knowledge/grammars/{starci → core}/playbook.yaml +3 -3
  11. package/runtime/knowledge/grammars/index.yaml +14 -23
  12. package/runtime/knowledge/grammars/offset-pop/playbook.yaml +2 -2
  13. package/runtime/knowledge/hfs/canon-pins.yaml +23 -23
  14. package/runtime/knowledge/hfs/fe-stack.yaml +1 -1
  15. package/runtime/knowledge/hfs/rules.yaml +83 -60
  16. package/runtime/modules/kernel/failure-codes.yaml +23 -13
  17. package/runtime/scripts/guards/git-policy.mjs +2 -3
  18. package/runtime/scripts/hfs/rule-catalog.mjs +2 -2
  19. package/runtime/scripts/hfs/rules/pipeline.mjs +5 -5
  20. package/runtime/scripts/lib/example-refs.mjs +10 -3
  21. package/runtime/scripts/lib/grammar-lint.mjs +16 -3
  22. package/runtime/scripts/lib/package-at.mjs +5 -6
  23. package/runtime/scripts/lib/roots.mjs +2 -2
  24. package/runtime/scripts/work/draw/draw-dna-findings.mjs +23 -12
  25. package/runtime/scripts/work/draw/draw-dna.mjs +14 -8
  26. package/scaffold/app.mjs +6 -6
  27. package/src/main.mjs +3 -3
  28. package/sync/hygiene.mjs +1 -1
  29. package/sync/index.mjs +15 -2
  30. package/sync/managed.mjs +3 -2
  31. package/templates/app/codegen-graphql.mjs.partial +4 -4
  32. package/templates/app/quality-config/sonar-project.properties +4 -2
  33. package/runtime/engine/canonical-json.mjs +0 -14
  34. package/runtime/engine/db/ledger-open.mjs +0 -38
  35. package/runtime/engine/db/ledger-paths.mjs +0 -87
  36. package/runtime/engine/db/ledger.mjs +0 -1068
  37. package/runtime/engine/db/provision-authority.mjs +0 -76
  38. package/runtime/engine/db/provision-operations.mjs +0 -146
  39. package/runtime/packages/grammar/scripts/build-stamp.mjs +0 -98
  40. package/runtime/scripts/agent/codex-app-server.mjs +0 -79
  41. package/runtime/scripts/agent/codex-mirror-source.mjs +0 -34
  42. package/runtime/scripts/agent/host-agents.mjs +0 -80
  43. package/runtime/scripts/agent/launch-trust-policy.mjs +0 -53
  44. package/runtime/scripts/agent/trust-launch.mjs +0 -191
  45. package/runtime/scripts/agent/trust.mjs +0 -523
  46. package/runtime/scripts/api/fs/temp-path.mjs +0 -8
  47. package/runtime/scripts/api/git/diff-names.mjs +0 -8
  48. package/runtime/scripts/api/git/head-time.mjs +0 -10
  49. package/runtime/scripts/api/git/index-lock.mjs +0 -78
  50. package/runtime/scripts/api/git/log.mjs +0 -5
  51. package/runtime/scripts/api/git/merge-tree.mjs +0 -5
  52. package/runtime/scripts/api/git/rev-list.mjs +0 -5
  53. package/runtime/scripts/api/git/rev-parse.mjs +0 -13
  54. package/runtime/scripts/api/git/status-query.mjs +0 -5
  55. package/runtime/scripts/api/git/worktree-list-query.mjs +0 -5
  56. package/runtime/scripts/api/node/spawn-node.mjs +0 -6
  57. package/runtime/scripts/api/npm/dist-tag.mjs +0 -8
  58. package/runtime/scripts/api/npm/lib.mjs +0 -32
  59. package/runtime/scripts/api/npm/pack-dry-run.mjs +0 -9
  60. package/runtime/scripts/api/npm/run-npm.mjs +0 -6
  61. package/runtime/scripts/api/npm/view.mjs +0 -30
  62. package/runtime/scripts/api/npm/whoami.mjs +0 -8
  63. package/runtime/scripts/api/process/hide-child-windows.mjs +0 -31
  64. package/runtime/scripts/api/process/kill-tree.mjs +0 -22
  65. package/runtime/scripts/api/process/lib.mjs +0 -125
  66. package/runtime/scripts/api/process/probe-guard-command.mjs +0 -74
  67. package/runtime/scripts/api/process/process-list.mjs +0 -28
  68. package/runtime/scripts/api/process/system-tool.mjs +0 -37
  69. package/runtime/scripts/gates/canon-digest.mjs +0 -81
  70. package/runtime/scripts/gates/canon-pins.mjs +0 -119
  71. package/runtime/scripts/gates/gate-input.mjs +0 -98
  72. package/runtime/scripts/gates/gate.mjs +0 -636
  73. package/runtime/scripts/gates/grammar-dist.mjs +0 -206
  74. package/runtime/scripts/gates/output.mjs +0 -40
  75. package/runtime/scripts/gates/runtime-package-selection.mjs +0 -27
  76. package/runtime/scripts/gates/test-world-run.mjs +0 -252
  77. package/runtime/scripts/gates/type-impact.mjs +0 -277
  78. package/runtime/scripts/gates/type-program.mjs +0 -113
  79. package/runtime/scripts/guards/assigned-command.mjs +0 -12
  80. package/runtime/scripts/guards/call-rights.mjs +0 -47
  81. package/runtime/scripts/guards/command-guard.mjs +0 -468
  82. package/runtime/scripts/guards/command-policy.mjs +0 -342
  83. package/runtime/scripts/guards/critic-reach.mjs +0 -82
  84. package/runtime/scripts/guards/deps-guard.mjs +0 -150
  85. package/runtime/scripts/guards/env-dump-verdict.mjs +0 -89
  86. package/runtime/scripts/guards/guards-root.mjs +0 -27
  87. package/runtime/scripts/guards/hook-install.mjs +0 -343
  88. package/runtime/scripts/guards/hook-verdict.mjs +0 -12
  89. package/runtime/scripts/guards/install-verdict.mjs +0 -41
  90. package/runtime/scripts/guards/kernel-seat.mjs +0 -63
  91. package/runtime/scripts/guards/launch-verdict.mjs +0 -74
  92. package/runtime/scripts/guards/op-starci.mjs +0 -16
  93. package/runtime/scripts/guards/op-write-scope.mjs +0 -27
  94. package/runtime/scripts/guards/orca-self-lifecycle.mjs +0 -39
  95. package/runtime/scripts/guards/process-kill-verdict.mjs +0 -106
  96. package/runtime/scripts/guards/protected-zone.mjs +0 -56
  97. package/runtime/scripts/guards/refusals.mjs +0 -27
  98. package/runtime/scripts/guards/rights.mjs +0 -268
  99. package/runtime/scripts/guards/seat-tools.mjs +0 -47
  100. package/runtime/scripts/guards/shell-commands.mjs +0 -328
  101. package/runtime/scripts/guards/supervisor-seat.mjs +0 -80
  102. package/runtime/scripts/guards/verdict-process.mjs +0 -81
  103. package/runtime/scripts/lib/app-data.mjs +0 -11
  104. package/runtime/scripts/lib/canon-binding-text.mjs +0 -129
  105. package/runtime/scripts/lib/check-format.mjs +0 -7
  106. package/runtime/scripts/lib/failure-code-reads.mjs +0 -100
  107. package/runtime/scripts/lib/failure-code-template-errors.mjs +0 -161
  108. package/runtime/scripts/lib/git-dir.mjs +0 -120
  109. package/runtime/scripts/lib/guard-command.mjs +0 -24
  110. package/runtime/scripts/lib/interrupt-cleanup.mjs +0 -38
  111. package/runtime/scripts/lib/json-formats.mjs +0 -34
  112. package/runtime/scripts/lib/managed-json.mjs +0 -51
  113. package/runtime/scripts/lib/menu-parts.mjs +0 -11
  114. package/runtime/scripts/lib/op-gate.mjs +0 -15
  115. package/runtime/scripts/lib/provision-control-failure.mjs +0 -32
  116. package/runtime/scripts/lib/release-version.mjs +0 -134
  117. package/runtime/scripts/lib/runtime-location.mjs +0 -21
  118. package/runtime/scripts/lib/runtime-package-inventory.mjs +0 -21
  119. package/runtime/scripts/lib/runtime-package-manifests.mjs +0 -84
  120. package/runtime/scripts/lib/runtime-publication-tags.mjs +0 -56
  121. package/runtime/scripts/lib/seat-env.mjs +0 -43
  122. package/runtime/scripts/lib/sonar-new-code.mjs +0 -58
  123. package/runtime/scripts/lib/source-package-imports.mjs +0 -111
  124. package/runtime/scripts/lib/tar-files.mjs +0 -39
  125. package/runtime/scripts/lib/uat-run-folder.mjs +0 -118
  126. package/runtime/scripts/lib/worktree-record.mjs +0 -14
  127. package/runtime/scripts/machine/ask-receipts.mjs +0 -113
  128. package/runtime/scripts/machine/evidence-store.mjs +0 -223
  129. package/runtime/scripts/machine/home.mjs +0 -216
  130. package/runtime/scripts/machine/host-lock.mjs +0 -184
  131. package/runtime/scripts/machine/lock-recovery.mjs +0 -55
  132. package/runtime/scripts/machine/roles-contract.mjs +0 -90
  133. package/runtime/scripts/machine/runtime-change.mjs +0 -20
  134. package/runtime/scripts/machine/workflow-tree.mjs +0 -122
  135. package/runtime/scripts/reconciler/checklist-items.mjs +0 -7
  136. package/runtime/scripts/reconciler/guard-row.mjs +0 -25
  137. package/runtime/scripts/work/brand/brand-colour.mjs +0 -125
  138. package/runtime/scripts/work/brand/brand-tokens.mjs +0 -167
  139. package/runtime/scripts/work/brand/brand-value-source.mjs +0 -106
  140. package/runtime/scripts/work/brand/brand.mjs +0 -892
  141. package/runtime/scripts/work/draw/draw-layer.mjs +0 -306
  142. package/runtime/scripts/work/draw/draw-rationale-page.mjs +0 -237
  143. package/runtime/scripts/work/draw/draw-rationale-ref.mjs +0 -14
  144. package/runtime/scripts/work/draw/draw-rationale.mjs +0 -353
  145. package/runtime/scripts/work/draw/draw-source.mjs +0 -412
  146. package/runtime/scripts/work/draw/draw-taste.mjs +0 -278
  147. package/runtime/scripts/work/draw/draw-typecheck.mjs +0 -72
  148. package/runtime/scripts/work/draw/rationale-findings.mjs +0 -205
  149. package/runtime/scripts/work/draw-grammar.mjs +0 -175
  150. package/runtime/scripts/work/draw-render-page.mjs +0 -188
  151. package/runtime/scripts/work/draw-render.mjs +0 -626
  152. package/runtime/scripts/work/png.mjs +0 -237
  153. package/runtime/scripts/work/render-tools.mjs +0 -64
@@ -1,129 +0,0 @@
1
- // Canon release binding edits preserve authored profile text and update only the selected binding's identity.
2
-
3
- const KEY = /^[A-Za-z_][A-Za-z0-9_-]*$/;
4
-
5
- function supportedBinding(value) {
6
- const keys = typeof value === 'string' ? value.split('.') : [];
7
- return (keys.length === 1 && keys[0] === 'canon') || (keys.length === 2 && keys[0] === 'familyCanons' && KEY.test(keys[1]));
8
- }
9
-
10
- function lineEntry(line, index) {
11
- const text = line.slice(0, line.length - lineEnding(line).length);
12
- if (!text.trim() || text.trimStart().startsWith('#')) return null;
13
- let indent = 0;
14
- while (text[indent] === ' ') indent++;
15
- const colon = text.indexOf(':', indent);
16
- const key = colon < 0 ? '' : text.slice(indent, colon);
17
- if (!KEY.test(key)) return { index, indent };
18
- let valueAt = colon + 1;
19
- while (text[valueAt] === ' ' || text[valueAt] === '\t') valueAt++;
20
- return { index, indent, key, prefix: text.slice(0, valueAt), value: text.slice(valueAt) };
21
- }
22
-
23
- function lineEnding(line) {
24
- if (line.endsWith('\r\n')) return '\r\n';
25
- return line.endsWith('\n') ? '\n' : '';
26
- }
27
-
28
- function linesOf(text) {
29
- const lines = [];
30
- let start = 0;
31
- for (let index = 0; index < text.length; index++) {
32
- if (text[index] !== '\n') continue;
33
- lines.push(text.slice(start, index + 1));
34
- start = index + 1;
35
- }
36
- if (start < text.length) lines.push(text.slice(start));
37
- return lines;
38
- }
39
-
40
- function child(entries, parent, key, at) {
41
- const nested = entries.filter(entry => entry.index > parent.start && entry.index < parent.end && entry.indent > parent.indent);
42
- const directIndent = Math.min(...nested.map(entry => entry.indent));
43
- const found = nested.filter(entry => entry.indent === directIndent && entry.key === key);
44
- if (found.length !== 1) throw new Error(`${at}: expected one ${key} field, found ${found.length}`);
45
- const entry = found[0];
46
- const boundary = entries.find(candidate => candidate.index > entry.index && candidate.index < parent.end && candidate.indent <= entry.indent);
47
- return { ...entry, start: entry.index, end: boundary?.index ?? parent.end };
48
- }
49
-
50
- function mapping(entries, parent, key, at) {
51
- const node = child(entries, parent, key, at);
52
- if (node.value.trim() && !node.value.trim().startsWith('#')) throw new Error(`${at}.${key}: expected a block mapping`);
53
- return node;
54
- }
55
-
56
- function scalar(entries, parent, key, at) {
57
- const node = child(entries, parent, key, at);
58
- if (!node.value.trim() || node.value.trim().startsWith('#')) throw new Error(`${at}.${key}: expected a scalar value`);
59
- return node;
60
- }
61
-
62
- function scalarParts(value) {
63
- const quote = value[0] === "'" || value[0] === '"' ? value[0] : null;
64
- let end = quote ? value.indexOf(quote, 1) : 0;
65
- if (quote && end < 1) return null;
66
- if (quote) end++;
67
- else {
68
- while (end < value.length && value[end].trim() && !['#', "'", '"'].includes(value[end])) end++;
69
- }
70
- if (!end) return null;
71
- let tail = end;
72
- while (value[tail] === ' ' || value[tail] === '\t') tail++;
73
- if (tail < value.length && (tail === end || value[tail] !== '#')) return null;
74
- return { value: quote ? value.slice(1, end - 1) : value.slice(0, end), suffix: value.slice(end) };
75
- }
76
-
77
- function packageValue(value) {
78
- return scalarParts(value)?.value;
79
- }
80
-
81
- function replaceScalar(lines, node, value) {
82
- const parts = scalarParts(node.value);
83
- if (!parts) throw new Error(`Cannot replace malformed scalar ${node.key}`);
84
- lines[node.index] = `${node.prefix}${value}${parts.suffix}${lineEnding(lines[node.index])}`;
85
- }
86
-
87
- function bindingNode(entries, root, row) {
88
- const at = `profiles.${row.profile}.${row.binding}`;
89
- let node = mapping(entries, root, 'profiles', 'document');
90
- node = mapping(entries, node, row.profile, 'profiles');
91
- for (const key of row.binding.split('.')) node = mapping(entries, node, key, at);
92
- const declared = scalar(entries, node, 'package', at);
93
- if (packageValue(declared.value) !== row.package) throw new Error(`${at}: package does not match ${row.package}`);
94
- return { node, at };
95
- }
96
-
97
- /**
98
- * Render release version and digest updates at their exact profile binding paths without reserializing authored YAML.
99
- * Unrelated bytes, comments and newline style are preserved. Missing or duplicate mapping paths, unexpected packages,
100
- * incomplete digest blocks and invalid replacement identities throw before any caller writes the returned text.
101
- * @param {string} original - The authored code-pattern profile YAML.
102
- * @param {Array<{profile: string, binding: string, package: string, to: {version: string, value: string, files: number}}>} rows - Exact canon or family binding identities to replace.
103
- * @returns {string} Updated profile YAML; the input is returned unchanged when no rows are supplied.
104
- */
105
- export function renderCanonBindingChanges(original, rows) {
106
- if (typeof original !== 'string' || !Array.isArray(rows)) throw new Error('canon binding changes need YAML text and identity rows');
107
- const lines = linesOf(original);
108
- const entries = lines.map((line, index) => lineEntry(line, index)).filter(Boolean);
109
- const root = { start: -1, end: lines.length, indent: -1 };
110
- const selected = new Set();
111
- for (const row of rows) {
112
- if (!KEY.test(row?.profile ?? '') || !supportedBinding(row?.binding) || typeof row?.package !== 'string' || !row.package) throw new Error('canon binding changes need a supported profile, package and binding path');
113
- const identity = `${row.profile}.${row.binding}`;
114
- if (selected.has(identity)) throw new Error(`${identity}: duplicate binding change`);
115
- selected.add(identity);
116
- if (!/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?$/.test(row.to?.version ?? '') || !/^[a-f0-9]{64}$/i.test(row.to?.value ?? '')
117
- || !Number.isSafeInteger(row.to?.files) || row.to.files < 1) throw new Error(`${identity}: invalid replacement version or digest`);
118
- const { node, at } = bindingNode(entries, root, row);
119
- const version = scalar(entries, node, 'version', at);
120
- const digest = mapping(entries, node, 'contentDigest', at);
121
- const value = scalar(entries, digest, 'value', `${at}.contentDigest`);
122
- const files = scalar(entries, digest, 'files', `${at}.contentDigest`);
123
- replaceScalar(lines, version, row.to.version);
124
- // A hexadecimal digest can contain digits only; quoting preserves its string identity in YAML.
125
- replaceScalar(lines, value, `'${row.to.value}'`);
126
- replaceScalar(lines, files, row.to.files);
127
- }
128
- return lines.join('');
129
- }
@@ -1,7 +0,0 @@
1
- // check-format.mjs — the one-line-per-check report every canon check CLI shares.
2
-
3
- const MARK = { pass: 'pass', fail: 'FAIL', skip: 'skip' };
4
-
5
- /** `header` plus one ' [outcome] id: detail' line per check of `checks`, joined with '\n'. */
6
- export const formatCheckLines = (header, checks) =>
7
- [header, ...(checks ?? []).map((entry) => ` [${MARK[entry.outcome]}] ${entry.id}: ${entry.detail}`)].join('\n');
@@ -1,100 +0,0 @@
1
- // failure-code-reads.mjs - the UPPER_SNAKE literals of a script that READ a value another producer made, or only describe one in a comment,
2
- // and so are not emitted failure codes (scripts/checks/check-failure-codes.mjs adds these quote offsets to its exact-AST exclusions).
3
- // - a literal inside a comment (a name mentioned in prose is not emitted);
4
- // - an operand of ===, !==, == or != against a non-literal, or a `case` label (the code is compared, not produced);
5
- // - an argument of a string or collection query (endsWith, startsWith, includes, indexOf, has, get);
6
- // - an element of an array literal whose only use here is `.includes(x)` / `.indexOf(x)` against a non-literal (a membership test);
7
- // - the symbol half of a (symbol, package) argument pair such as `providersOf(root, 'APP_GUARD', '@nestjs/core')`: a name another package exports;
8
- // - a literal the script declares as an enum member by identity (`Scalar.QUOTE_DOUBLE = "QUOTE_DOUBLE"`), wherever it then appears: the value of an enum-like constant, not a code;
9
- // - the value of a property named `type` (a discriminator of someone else's API, such as a token type).
10
- // Emission sites (code:, reason:, finding(...), thrown messages, code lists) are never matched here. Proven by the package spec
11
- // src/failure-code-reads.spec.mjs over the bundled copy of this file.
12
-
13
- const EQUALITY = new Set(['===', '!==', '==', '!=']);
14
- const QUERY_METHODS = new Set(['endsWith', 'startsWith', 'includes', 'indexOf', 'has', 'get']);
15
- const MEMBERSHIP_METHODS = new Set(['includes', 'indexOf']);
16
-
17
- const isText = (t, node) => t.isStringLiteralLike(node);
18
-
19
- /** Whether `node` is the operand of an equality against a value that is not itself a literal. */
20
- function comparedValue(t, source, node) {
21
- const parent = node.parent;
22
- if (!parent || !t.isBinaryExpression(parent) || !EQUALITY.has(parent.operatorToken.getText(source))) return false;
23
- const other = parent.left === node ? parent.right : parent.left;
24
- return !isText(t, other);
25
- }
26
-
27
- /** Whether `node` is an argument of a string/collection query call such as `key.endsWith('X_Y')`. */
28
- function queryArgument(t, node) {
29
- const call = node.parent;
30
- return Boolean(call && t.isCallExpression(call) && call.arguments.includes(node)
31
- && t.isPropertyAccessExpression(call.expression) && QUERY_METHODS.has(call.expression.name.text));
32
- }
33
-
34
- /** Whether `node` is an element of an array literal that is only tested with `.includes(value)` for a non-literal value. */
35
- function membershipElement(t, node) {
36
- const array = node.parent;
37
- if (!array || !t.isArrayLiteralExpression(array)) return false;
38
- const access = array.parent;
39
- if (!access || !t.isPropertyAccessExpression(access) || access.expression !== array || !MEMBERSHIP_METHODS.has(access.name.text)) return false;
40
- const call = access.parent;
41
- return Boolean(call && t.isCallExpression(call) && call.expression === access && call.arguments.length >= 1 && !isText(t, call.arguments[0]));
42
- }
43
-
44
- const PACKAGE_SPECIFIER = /^(?:@[\w.-]+\/[\w.-]+(?:\/[\w./-]+)?|\.{1,2}\/[\w./-]+)$/;
45
-
46
- /** Whether `node` is a call argument directly followed by a package or relative module specifier: the name of a symbol that module exports. */
47
- function importedSymbol(t, node) {
48
- const call = node.parent;
49
- if (!call || !t.isCallExpression(call)) return false;
50
- const next = call.arguments[call.arguments.indexOf(node) + 1];
51
- return Boolean(next && isText(t, next) && PACKAGE_SPECIFIER.test(next.text));
52
- }
53
-
54
- const caseLabel = (t, node) => Boolean(node.parent && t.isCaseClause(node.parent) && node.parent.expression === node);
55
-
56
- const discriminator = (t, node) => Boolean(node.parent && t.isPropertyAssignment(node.parent) && node.parent.initializer === node
57
- && t.isIdentifier(node.parent.name) && node.parent.name.text === 'type');
58
-
59
- /** The [pos, end) ranges of every comment of `source`. */
60
- function commentRanges(t, source) {
61
- const text = source.text, ranges = [];
62
- const visit = (node) => {
63
- for (const range of t.getLeadingCommentRanges(text, node.getFullStart()) ?? []) ranges.push([range.pos, range.end]);
64
- for (const range of t.getTrailingCommentRanges(text, node.end) ?? []) ranges.push([range.pos, range.end]);
65
- t.forEachChild(node, visit);
66
- };
67
- visit(source);
68
- return ranges;
69
- }
70
-
71
- /**
72
- * The offsets to leave out of UPPER literal discovery for a parsed script: the opening quote of every read literal, and the start
73
- * of every `patterns` match (RegExp with the g flag) that lies inside a comment.
74
- */
75
- export function nonEmittingOffsets(t, source, patterns = []) {
76
- const offsets = new Set();
77
- const members = new Set();
78
- const collect = (node) => {
79
- if (t.isBinaryExpression(node) && node.operatorToken.kind === t.SyntaxKind.EqualsToken && t.isPropertyAccessExpression(node.left)
80
- && isText(t, node.right) && node.right.text === node.left.name.text) members.add(node.right.text);
81
- t.forEachChild(node, collect);
82
- };
83
- collect(source);
84
- const visit = (node) => {
85
- if (isText(t, node) && (members.has(node.text) || comparedValue(t, source, node) || queryArgument(t, node) || membershipElement(t, node) || caseLabel(t, node) || importedSymbol(t, node) || discriminator(t, node))) {
86
- offsets.add(node.getStart(source));
87
- }
88
- t.forEachChild(node, visit);
89
- };
90
- visit(source);
91
- const ranges = commentRanges(t, source);
92
- if (ranges.length) {
93
- for (const pattern of patterns) {
94
- for (const match of source.text.matchAll(pattern)) {
95
- if (ranges.some(([pos, end]) => match.index >= pos && match.index < end)) offsets.add(match.index);
96
- }
97
- }
98
- }
99
- return offsets;
100
- }
@@ -1,161 +0,0 @@
1
- // Canonical platform templates can own shared application failures as well as runtime findings.
2
- // A catalog entry stays current only when production code instantiates its genuine DomainError enum member.
3
- import path from 'node:path';
4
-
5
- const CODE = /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/;
6
- const exported = (t, node) => node.modifiers?.some((modifier) => modifier.kind === t.SyntaxKind.ExportKeyword);
7
- const textName = (t, node) => node && (t.isIdentifier(node) || t.isStringLiteralLike(node)) ? node.text : null;
8
-
9
- /** An import path resolves inside the virtual backend source tree declared by the pattern files. */
10
- function sourceTarget(specifier, owner, sources) {
11
- let target = null;
12
- if (specifier.startsWith('@modules/')) target = `src/modules/${specifier.slice('@modules/'.length)}`;
13
- else if (specifier.startsWith('.')) target = path.posix.normalize(path.posix.join(path.posix.dirname(owner), specifier));
14
- if (target === null) return null;
15
- return [target, `${target}.ts`, `${target}/index.ts`].find((candidate) => sources.has(candidate)) ?? null;
16
- }
17
-
18
- /** Index module bindings and explicit exports without importing or running a template. */
19
- function moduleInfo(t, name, entry) {
20
- const source = t.createSourceFile(name, entry.text, t.ScriptTarget.Latest, true, t.ScriptKind.TS);
21
- const info = { name, file: entry.file, source, imports: new Map(), namespaces: new Map(), locals: new Map(), exports: new Map(), shadows: new Set() };
22
- for (const statement of source.statements) {
23
- if (t.isImportDeclaration(statement)) indexImport(t, info, statement);
24
- if (t.isExportDeclaration(statement)) indexExport(t, info, statement);
25
- if ((t.isEnumDeclaration(statement) || t.isClassDeclaration(statement)) && statement.name) {
26
- const key = statement.name.text;
27
- info.locals.set(key, statement);
28
- if (exported(t, statement)) info.exports.set(key, { local: key });
29
- }
30
- }
31
- const visit = (node) => {
32
- const binding = scopedBinding(t, node);
33
- if (binding) info.shadows.add(binding);
34
- t.forEachChild(node, visit);
35
- };
36
- visit(source);
37
- return info;
38
- }
39
-
40
- /** A nested declaration makes a text-only binding ambiguous; it cannot supply catalog evidence. */
41
- function scopedBinding(t, node) {
42
- const named = t.isVariableDeclaration(node) || t.isParameter(node) || t.isBindingElement(node)
43
- || t.isClassDeclaration(node) || t.isEnumDeclaration(node) || t.isFunctionDeclaration(node);
44
- if (!named || !node.name || !t.isIdentifier(node.name)) return null;
45
- let scope = node.parent;
46
- while (scope && !t.isSourceFile(scope)) {
47
- if (t.isBlock(scope) || t.isFunctionLike(scope)) return node.name.text;
48
- scope = scope.parent;
49
- }
50
- return null;
51
- }
52
-
53
- function indexImport(t, info, statement) {
54
- const bindings = statement.importClause?.namedBindings;
55
- if (!bindings || !t.isStringLiteralLike(statement.moduleSpecifier)) return;
56
- const specifier = statement.moduleSpecifier.text;
57
- if (t.isNamespaceImport(bindings)) info.namespaces.set(bindings.name.text, specifier);
58
- else for (const binding of bindings.elements) info.imports.set(binding.name.text, { name: (binding.propertyName ?? binding.name).text, specifier });
59
- }
60
-
61
- function indexExport(t, info, statement) {
62
- const clause = statement.exportClause;
63
- if (!clause || !t.isNamedExports(clause)) return;
64
- const specifier = statement.moduleSpecifier && t.isStringLiteralLike(statement.moduleSpecifier) ? statement.moduleSpecifier.text : null;
65
- for (const binding of clause.elements) {
66
- const name = (binding.propertyName ?? binding.name).text;
67
- info.exports.set(binding.name.text, specifier ? { name, specifier } : { local: name });
68
- }
69
- }
70
-
71
- /** Only a declared binding or a named import/export can establish an error family or enum identity. */
72
- function localValue(info, name, modules, seen = new Set()) {
73
- const identity = `${info.name}:${name}`;
74
- if (seen.has(identity) || info.shadows.has(name)) return null;
75
- const visited = new Set(seen).add(identity);
76
- if (info.locals.has(name)) return { info, declaration: info.locals.get(name) };
77
- const binding = info.imports.get(name);
78
- return binding ? importedValue(info, binding, modules, visited) : null;
79
- }
80
-
81
- function importedValue(info, binding, modules, seen) {
82
- const target = sourceTarget(binding.specifier, info.name, modules);
83
- const owner = target && modules.get(target);
84
- const identity = owner && `export:${owner.name}:${binding.name}`;
85
- if (!owner || seen.has(identity)) return null;
86
- const visited = new Set(seen).add(identity);
87
- const exposed = owner?.exports.get(binding.name);
88
- if (!exposed) return null;
89
- return exposed.local ? localValue(owner, exposed.local, modules, visited) : importedValue(owner, exposed, modules, visited);
90
- }
91
-
92
- const accessParts = (t, expression) => {
93
- if (t.isPropertyAccessExpression(expression) || t.isQualifiedName(expression)) {
94
- return { base: expression.expression ?? expression.left, member: (expression.name ?? expression.right).text };
95
- }
96
- if (t.isElementAccessExpression(expression) && t.isStringLiteralLike(expression.argumentExpression)) return { base: expression.expression, member: expression.argumentExpression.text };
97
- return null;
98
- };
99
-
100
- function expressionValue(t, info, expression, modules) {
101
- if (t.isIdentifier(expression)) return localValue(info, expression.text, modules);
102
- const access = accessParts(t, expression);
103
- const specifier = access && t.isIdentifier(access.base) && !info.shadows.has(access.base.text) && info.namespaces.get(access.base.text);
104
- return specifier ? importedValue(info, { specifier, name: access.member }, modules, new Set()) : null;
105
- }
106
-
107
- /** DomainError is the canonical imported base, not an identically named local class. */
108
- function domainBase(t, info, expression) {
109
- if (!t.isIdentifier(expression)) return false;
110
- const binding = info.imports.get(expression.text);
111
- return !info.shadows.has(expression.text) && binding?.name === 'DomainError' && binding.specifier === '@modules/platform/errors';
112
- }
113
-
114
- /** The generic enum on the real DomainError base owns the family's permitted codes. */
115
- function errorEnum(t, value, modules) {
116
- const node = value?.declaration;
117
- if (!node || !t.isClassDeclaration(node) || node.members.some(t.isConstructorDeclaration)) return null;
118
- const clause = node.heritageClauses?.find((item) => item.token === t.SyntaxKind.ExtendsKeyword);
119
- const base = clause?.types[0];
120
- const argument = base?.typeArguments?.[0];
121
- if (!base || !domainBase(t, value.info, base.expression) || base.typeArguments?.length !== 1 || !argument || !t.isTypeReferenceNode(argument)) return null;
122
- const enumeration = expressionValue(t, value.info, argument.typeName, modules);
123
- return enumeration && t.isEnumDeclaration(enumeration.declaration) ? enumeration : null;
124
- }
125
-
126
- /** A static code field survives only when no later spread or dynamic key can replace it. */
127
- function codeField(t, expression) {
128
- if (!expression || !t.isObjectLiteralExpression(expression)) return null;
129
- let code = null;
130
- for (const property of expression.properties) {
131
- if (t.isSpreadAssignment(property) || textName(t, property.name) === null) code = null;
132
- else if (textName(t, property.name) === 'code') code = t.isPropertyAssignment(property) ? property.initializer : null;
133
- }
134
- return code;
135
- }
136
-
137
- /** Constructor and code expression must resolve to the same declared enum, including through public barrels. */
138
- function constructedCode(t, info, node, modules) {
139
- const family = errorEnum(t, expressionValue(t, info, node.expression, modules), modules);
140
- const field = codeField(t, node.arguments?.[0]);
141
- const access = field && accessParts(t, field);
142
- const enumeration = access && expressionValue(t, info, access.base, modules);
143
- if (!family || !enumeration || enumeration.declaration !== family.declaration) return null;
144
- const member = family.declaration.members.find((item) => textName(t, item.name) === access.member);
145
- return member?.initializer && t.isStringLiteralLike(member.initializer) && CODE.test(member.initializer.text) ? member.initializer.text : null;
146
- }
147
-
148
- /** Actual typed error constructions in declared production platform templates, with their source locations. */
149
- export function templateErrorCodes(t, sources) {
150
- const modules = new Map([...sources].map(([name, entry]) => [name, moduleInfo(t, name, entry)]));
151
- const emitted = new Map();
152
- for (const info of modules.values()) {
153
- const visit = (node) => {
154
- const code = t.isNewExpression(node) && constructedCode(t, info, node, modules);
155
- if (code) emitted.set(code, { code, file: info.file, line: info.source.getLineAndCharacterOfPosition(node.getStart(info.source)).line + 1 });
156
- t.forEachChild(node, visit);
157
- };
158
- visit(info.source);
159
- }
160
- return [...emitted.values()];
161
- }
@@ -1,120 +0,0 @@
1
- // git-dir.mjs — a checkout's git directory and the git processes that may hold it, read without running git: the pure
2
- // half of the stale `.git/index.lock` recovery (scripts/api/git/index-lock.mjs indexLock removes the lock;
3
- // scripts/machine/lock-recovery.mjs reads the process table, records the removal and runs the command guard's pre-flight).
4
- import fs from 'node:fs';
5
- import path from 'node:path';
6
- import { pathKey } from './path-key.mjs';
7
-
8
- /** git.exe and its helpers (git-remote-https.exe, git-lfs.exe, ...). */
9
- const GIT_IMAGE = /^git(?:-[\w.-]+)?(?:\.exe)?$/i;
10
- /** Whether a process image name is git or one of its helpers. */
11
- export const isGitImage = (name) => GIT_IMAGE.test(String(name ?? ''));
12
- /** `file`'s lstat, or null when it cannot be read. */
13
- export const lockStat = (file) => { try { return fs.lstatSync(file); } catch { return null; } };
14
-
15
- const GITDIR_LINE = new RegExp([
16
- '^gitdir:',
17
- String.raw`\s*(.+)$`,
18
- ].join(''), 'm');
19
-
20
- /** The git dir of the checkout at `repo`: `.git` itself, or the `gitdir:` a worktree's `.git` file names. Null when none. */
21
- export function gitDirOf(repo) {
22
- const dotGit = path.join(path.resolve(repo), '.git');
23
- let st;
24
- try { st = fs.lstatSync(dotGit); } catch { return null; }
25
- if (st.isDirectory()) return dotGit;
26
- if (!st.isFile()) return null;
27
- try {
28
- const m = GITDIR_LINE.exec(fs.readFileSync(dotGit, 'utf8'));
29
- return m ? path.resolve(path.dirname(dotGit), m[1].trim()) : null;
30
- } catch { return null; }
31
- }
32
-
33
- /** The checkout `start` is in: the nearest directory at or above it holding `.git`, or null. */
34
- export function checkoutOf(start) {
35
- for (let dir = path.resolve(String(start ?? '.')); ; dir = path.dirname(dir)) {
36
- if (lockStat(path.join(dir, '.git'))) return dir;
37
- if (path.dirname(dir) === dir) return null;
38
- }
39
- }
40
-
41
- const FULL_SHA = /^[0-9a-f]{40}$/;
42
-
43
- function looseHeadRef(gitDir, common, ref, commonOnly) {
44
- for (const dir of commonOnly ? [common] : [gitDir, common]) {
45
- try {
46
- const loose = fs.readFileSync(path.join(dir, ref), 'utf8').trim();
47
- if (FULL_SHA.test(loose)) return loose;
48
- } catch { /* packed */ }
49
- }
50
- return null;
51
- }
52
-
53
- function packedHeadRef(common, ref) {
54
- for (const line of fs.readFileSync(path.join(common, 'packed-refs'), 'utf8').split('\n')) {
55
- const [sha, name] = line.trim().split(' ');
56
- if (name === ref && FULL_SHA.test(sha)) return sha;
57
- }
58
- return null;
59
- }
60
-
61
- const HEADS_REF_LINE = new RegExp([
62
- String.raw`^ref:\s*(refs\/heads\/\S+)`,
63
- '$',
64
- ].join(''));
65
- const ANY_REF_LINE = new RegExp([
66
- String.raw`^ref:\s*(.+)`,
67
- '$',
68
- ].join(''));
69
- /**
70
- * The commit the HEAD of the checkout at `root` names, read from .git with no spawn; null when it cannot be told that
71
- * way (not a top level, reftable, an unreadable ref) and the read then runs live. `headsOnly` accepts only
72
- * refs/heads/* symbolic refs; a loose ref reads from the common dir, as git does for a linked worktree — plus the git
73
- * dir itself first unless `commonOnly`.
74
- */
75
- export function headShaOf(root, { headsOnly = false, commonOnly = false } = {}) {
76
- try {
77
- let gitDir = path.join(root, '.git');
78
- if (fs.statSync(gitDir).isFile()) {
79
- const pointer = GITDIR_LINE.exec(fs.readFileSync(gitDir, 'utf8'))?.[1];
80
- if (!pointer) return null;
81
- gitDir = path.resolve(root, pointer.trim());
82
- }
83
- const head = fs.readFileSync(path.join(gitDir, 'HEAD'), 'utf8').trim();
84
- if (FULL_SHA.test(head)) return head;
85
- const ref = headsOnly ? HEADS_REF_LINE.exec(head)?.[1] : ANY_REF_LINE.exec(head)?.[1]?.trim();
86
- if (!ref) return null;
87
- let common = gitDir;
88
- try { common = path.resolve(gitDir, fs.readFileSync(path.join(gitDir, 'commondir'), 'utf8').trim()); } catch { /* not a linked worktree */ }
89
- const loose = looseHeadRef(gitDir, common, ref, commonOnly);
90
- if (loose) return loose;
91
- return packedHeadRef(common, ref);
92
- } catch { /* no git metadata */ }
93
- return null;
94
- }
95
-
96
- /** Command-line words, double quotes grouping (Windows command lines quote paths with spaces). */
97
- const words = (line) => [...String(line ?? '').matchAll(/"([^"]*)"|(\S+)/g)].map((m) => m[1] ?? m[2]);
98
-
99
- /** The repositories a git command line names by -C, --git-dir or --work-tree (as given, unresolved). */
100
- function reposNamed(commandLine) {
101
- const w = words(commandLine), out = [];
102
- for (let i = 0; i < w.length; i += 1) {
103
- if (w[i] === '-C' || w[i] === '--git-dir' || w[i] === '--work-tree') { if (w[i + 1]) { out.push(w[i + 1]); } i += 1; continue; }
104
- const m = /^--(?:git-dir|work-tree)=(.+)$/.exec(w[i]);
105
- if (m) out.push(m[1]);
106
- }
107
- return out;
108
- }
109
-
110
- /**
111
- * Whether one git process may hold `repo`'s index: 'this' (it names the repo or a path inside it), 'other'
112
- * (every repository it names is elsewhere) or 'unknown' (it names none; its cwd may be this repo).
113
- */
114
- export function processOnRepo(proc, repo) {
115
- const named = reposNamed(proc?.commandLine);
116
- if (!named.length) return 'unknown';
117
- const key = pathKey(path.resolve(repo));
118
- const hit = named.some((p) => { const k = pathKey(path.resolve(p)); return k === key || k.startsWith(`${key}/`) || key.startsWith(`${k}/`); });
119
- return hit ? 'this' : 'other';
120
- }
@@ -1,24 +0,0 @@
1
- // guard-command.mjs — the one spelling of the hook command that registers a StarCi guard verb with an agent host.
2
- //
3
- // A host chooses the shell that runs a hook. The linked runtime supplies the absolute CLI path shared by launch trust
4
- // and preflight. Hook deadlines belong to the guard process; shell termination does not bound native children on Windows.
5
- import process from 'node:process';
6
- import path from 'node:path';
7
- import { readRuntimeRecord } from './runtime-location.mjs';
8
-
9
- const GUARD_COMMAND_END = /starci(?:\.mjs|\.cmd)?"? guard command$/;
10
- const quotedPath = (file) => `"${String(file).replaceAll('\\', '/')}"`;
11
-
12
- /** The command guard hook for the linked runtime; a missing or stale runtime record refuses registration. */
13
- export function toolGuardCommand({ home, nodePath = process.execPath, cliPath = null } = {}) {
14
- if (!cliPath) {
15
- const skipped = [];
16
- const installed = readRuntimeRecord({ home, skipped });
17
- if (!installed) throw new Error(skipped.join('; ') || 'no linked runtime; run starci runtime link');
18
- cliPath = path.join(installed.root, 'packages', 'cli', 'bin', 'starci.mjs');
19
- }
20
- return `${quotedPath(nodePath)} ${quotedPath(cliPath)} guard command`;
21
- }
22
-
23
- /** Recognize the direct Node hook and launcher spellings so trust preparation replaces an older registered guard. */
24
- export const isGuardCommand = (command) => GUARD_COMMAND_END.test(String(command ?? ''));
@@ -1,38 +0,0 @@
1
- // interrupt-cleanup.mjs - the one owner of "what a long verb leaves behind when it is interrupted". A verb that creates something outside its own return path (a lock, a temp file,
2
- // a container) registers the undo with `onInterrupt(undo)`; the undo runs when the process receives SIGINT, SIGTERM, SIGBREAK or SIGHUP (then the process exits with 128 + the signal number)
3
- // and when the process exits while the registration is still open, and never twice. The returned disposer removes the registration once the verb released the thing itself.
4
- // The process handlers are installed with the first registration and removed with the last. The registry takes the process as a seam (`proc`: on, off, exit).
5
- const SIGNALS = Object.freeze({ SIGHUP: 1, SIGINT: 2, SIGTERM: 15, SIGBREAK: 21 });
6
-
7
- /** A registry of undo functions bound to `proc`. */
8
- function createInterruptRegistry({ proc = process } = {}) {
9
- const undos = new Set();
10
- const handlers = new Map();
11
-
12
- const runAll = () => {
13
- for (const undo of [...undos].reverse()) {
14
- undos.delete(undo);
15
- try { undo(); } catch { /* one failing undo never stops the others */ }
16
- }
17
- };
18
- const uninstall = () => {
19
- for (const [event, handler] of handlers) proc.off(event, handler);
20
- handlers.clear();
21
- };
22
- const install = () => {
23
- if (handlers.size) return;
24
- for (const [signal, number] of Object.entries(SIGNALS)) handlers.set(signal, () => { runAll(); uninstall(); proc.exit(128 + number); });
25
- handlers.set('exit', runAll);
26
- for (const [event, handler] of handlers) proc.on(event, handler);
27
- };
28
-
29
- /** Register `undo`; returns the disposer that removes it (and the process handlers when it was the last). */
30
- const onInterrupt = (undo) => {
31
- undos.add(undo);
32
- install();
33
- return () => { undos.delete(undo); if (!undos.size) uninstall(); };
34
- };
35
- return { onInterrupt, pending: () => undos.size };
36
- }
37
-
38
- export const { onInterrupt } = createInterruptRegistry();
@@ -1,34 +0,0 @@
1
- // json-formats.mjs - the JSON files whose format a tool requires, declared once by rule (a basename, a tool-fixed folder or a managed
2
- // fragment) and not file by file. A file of a listed format is not authored declarative source: npm, tsc, turbo, next-intl, Claude Code,
3
- // the stack tooling and Keycloak read it as JSON and nothing else, so no YAML or JS form of it exists. Everything else that is authored
4
- // config belongs in YAML, and the json-exceptions check (scripts/checks/check-json-exceptions.mjs) refuses it. Proven by the package spec
5
- // src/json-formats.spec.mjs over the bundled copy of this file.
6
-
7
- /** The base name of a slash path. */
8
- const baseOf = (rel) => rel.slice(rel.lastIndexOf('/') + 1);
9
-
10
- /**
11
- * The declared formats: `id`, the `tool` that fixes the format and a `match(rel)` over a skill-relative slash path. A format names a
12
- * kind of file (a base name or a folder the tool reads by position), never one path of this repository.
13
- */
14
- export const JSON_FORMATS = Object.freeze([
15
- { id: 'npm-manifest', tool: 'npm', match: (rel) => baseOf(rel) === 'package.json' || baseOf(rel) === 'package-lock.json' },
16
- { id: 'typescript-config', tool: 'tsc', match: (rel) => /^tsconfig(\.[A-Za-z0-9-]+)*\.json$/.test(baseOf(rel)) },
17
- { id: 'shared-tsconfig-preset', tool: 'tsc (extends)', match: (rel) => /^packages\/tsconfig\/[^/]+\.json$/.test(rel) },
18
- { id: 'turbo-pipeline', tool: 'turbo', match: (rel) => baseOf(rel) === 'turbo.json' },
19
- { id: 'nest-cli', tool: '@nestjs/cli', match: (rel) => baseOf(rel) === 'nest-cli.json' },
20
- { id: 'tree-kind', tool: 'starci hfs (hfs.json at a tree root)', match: (rel) => baseOf(rel) === 'hfs.json' },
21
- { id: 'claude-code-settings', tool: 'Claude Code', match: (rel) => /(^|\/)\.claude\/settings\.json$/.test(rel) },
22
- { id: 'i18n-message-catalog', tool: 'next-intl', match: (rel) => /(^|\/)modules\/i18n\/messages\/[a-z]{2}(-[A-Za-z]+)?\.json$/.test(rel) },
23
- { id: 'stack-environment', tool: 'starci stack (.starcistacks layout)', match: (rel) => /(^|\/)\.starcistacks\/[^/]+\/environment\.json$/.test(rel) },
24
- { id: 'stack-infra-metadata', tool: 'starci stack (.starcistacks layout)', match: (rel) => /(^|\/)\.starcistacks\/[^/]+\/infra\/metadata\.json$/.test(rel) },
25
- { id: 'keycloak-realm-import', tool: 'Keycloak realm import', match: (rel) => /(^|\/)\.starcistacks\/[^/]+\/infra\/compose\/realm-[^/]+\.json$/.test(rel) },
26
- { id: 'package-scripts-fragment', tool: 'npm (scripts rendered into package.json)', match: (rel) => /^packages\/hfs\/templates\/app\/package-scripts[^/]*\/[^/]+\.json$/.test(rel) },
27
- { id: 'async-event-contract', tool: 'starci app emit', match: (rel) => /(^|\/)be\/contracts\/[^/]+\/events(\.pin)?\.json$/.test(rel) },
28
- { id: 'operation-contract', tool: 'starci app emit', match: (rel) => /(^|\/)be\/contracts\/[^/]+\/openapi\.json$/.test(rel) },
29
- ]);
30
-
31
- /** The declared format that fixes `rel`, or null: the file is authored JSON the check must refuse. */
32
- export function jsonFormatOf(rel) {
33
- return JSON_FORMATS.find((format) => format.match(rel)) ?? null;
34
- }