@ploaness/governance 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -0
  3. package/dist/access-boundary.d.ts +41 -0
  4. package/dist/access-boundary.d.ts.map +1 -0
  5. package/dist/access-boundary.js +26 -0
  6. package/dist/access-boundary.js.map +1 -0
  7. package/dist/agent-references.d.ts +12 -0
  8. package/dist/agent-references.d.ts.map +1 -0
  9. package/dist/agent-references.js +53 -0
  10. package/dist/agent-references.js.map +1 -0
  11. package/dist/architecture-layers.d.ts +11 -0
  12. package/dist/architecture-layers.d.ts.map +1 -0
  13. package/dist/architecture-layers.js +47 -0
  14. package/dist/architecture-layers.js.map +1 -0
  15. package/dist/asset-policy.d.ts +138 -0
  16. package/dist/asset-policy.d.ts.map +1 -0
  17. package/dist/asset-policy.js +312 -0
  18. package/dist/asset-policy.js.map +1 -0
  19. package/dist/banned-typography.d.ts +13 -0
  20. package/dist/banned-typography.d.ts.map +1 -0
  21. package/dist/banned-typography.js +78 -0
  22. package/dist/banned-typography.js.map +1 -0
  23. package/dist/bundle-budget.d.ts +16 -0
  24. package/dist/bundle-budget.d.ts.map +1 -0
  25. package/dist/bundle-budget.js +37 -0
  26. package/dist/bundle-budget.js.map +1 -0
  27. package/dist/commit-message.d.ts +37 -0
  28. package/dist/commit-message.d.ts.map +1 -0
  29. package/dist/commit-message.js +144 -0
  30. package/dist/commit-message.js.map +1 -0
  31. package/dist/config-references.d.ts +17 -0
  32. package/dist/config-references.d.ts.map +1 -0
  33. package/dist/config-references.js +54 -0
  34. package/dist/config-references.js.map +1 -0
  35. package/dist/container-failure.d.ts +40 -0
  36. package/dist/container-failure.d.ts.map +1 -0
  37. package/dist/container-failure.js +154 -0
  38. package/dist/container-failure.js.map +1 -0
  39. package/dist/container-files.d.ts +36 -0
  40. package/dist/container-files.d.ts.map +1 -0
  41. package/dist/container-files.js +74 -0
  42. package/dist/container-files.js.map +1 -0
  43. package/dist/container-freshness.d.ts +102 -0
  44. package/dist/container-freshness.d.ts.map +1 -0
  45. package/dist/container-freshness.js +129 -0
  46. package/dist/container-freshness.js.map +1 -0
  47. package/dist/dependency-freshness.d.ts +129 -0
  48. package/dist/dependency-freshness.d.ts.map +1 -0
  49. package/dist/dependency-freshness.js +161 -0
  50. package/dist/dependency-freshness.js.map +1 -0
  51. package/dist/doc-blocks.d.ts +13 -0
  52. package/dist/doc-blocks.d.ts.map +1 -0
  53. package/dist/doc-blocks.js +75 -0
  54. package/dist/doc-blocks.js.map +1 -0
  55. package/dist/document-references.d.ts +44 -0
  56. package/dist/document-references.d.ts.map +1 -0
  57. package/dist/document-references.js +94 -0
  58. package/dist/document-references.js.map +1 -0
  59. package/dist/editorconfig-policy.d.ts +24 -0
  60. package/dist/editorconfig-policy.d.ts.map +1 -0
  61. package/dist/editorconfig-policy.js +106 -0
  62. package/dist/editorconfig-policy.js.map +1 -0
  63. package/dist/environment-files.d.ts +42 -0
  64. package/dist/environment-files.d.ts.map +1 -0
  65. package/dist/environment-files.js +76 -0
  66. package/dist/environment-files.js.map +1 -0
  67. package/dist/file-roles.d.ts +39 -0
  68. package/dist/file-roles.d.ts.map +1 -0
  69. package/dist/file-roles.js +82 -0
  70. package/dist/file-roles.js.map +1 -0
  71. package/dist/generated-denial.d.ts +42 -0
  72. package/dist/generated-denial.d.ts.map +1 -0
  73. package/dist/generated-denial.js +87 -0
  74. package/dist/generated-denial.js.map +1 -0
  75. package/dist/history-policy.d.ts +32 -0
  76. package/dist/history-policy.d.ts.map +1 -0
  77. package/dist/history-policy.js +50 -0
  78. package/dist/history-policy.js.map +1 -0
  79. package/dist/image-assets.d.ts +14 -0
  80. package/dist/image-assets.d.ts.map +1 -0
  81. package/dist/image-assets.js +205 -0
  82. package/dist/image-assets.js.map +1 -0
  83. package/dist/index.d.ts +49 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +53 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/install-policy.d.ts +35 -0
  88. package/dist/install-policy.d.ts.map +1 -0
  89. package/dist/install-policy.js +61 -0
  90. package/dist/install-policy.js.map +1 -0
  91. package/dist/json-shapes.d.ts +86 -0
  92. package/dist/json-shapes.d.ts.map +1 -0
  93. package/dist/json-shapes.js +124 -0
  94. package/dist/json-shapes.js.map +1 -0
  95. package/dist/license-allowlist.d.ts +15 -0
  96. package/dist/license-allowlist.d.ts.map +1 -0
  97. package/dist/license-allowlist.js +157 -0
  98. package/dist/license-allowlist.js.map +1 -0
  99. package/dist/network-policy.d.ts +69 -0
  100. package/dist/network-policy.d.ts.map +1 -0
  101. package/dist/network-policy.js +173 -0
  102. package/dist/network-policy.js.map +1 -0
  103. package/dist/paired-assets.d.ts +34 -0
  104. package/dist/paired-assets.d.ts.map +1 -0
  105. package/dist/paired-assets.js +63 -0
  106. package/dist/paired-assets.js.map +1 -0
  107. package/dist/payload-access.d.ts +9 -0
  108. package/dist/payload-access.d.ts.map +1 -0
  109. package/dist/payload-access.js +168 -0
  110. package/dist/payload-access.js.map +1 -0
  111. package/dist/payload-policy.d.ts +18 -0
  112. package/dist/payload-policy.d.ts.map +1 -0
  113. package/dist/payload-policy.js +124 -0
  114. package/dist/payload-policy.js.map +1 -0
  115. package/dist/payload-source.d.ts +29 -0
  116. package/dist/payload-source.d.ts.map +1 -0
  117. package/dist/payload-source.js +84 -0
  118. package/dist/payload-source.js.map +1 -0
  119. package/dist/release-age.d.ts +23 -0
  120. package/dist/release-age.d.ts.map +1 -0
  121. package/dist/release-age.js +38 -0
  122. package/dist/release-age.js.map +1 -0
  123. package/dist/request-limits.d.ts +39 -0
  124. package/dist/request-limits.d.ts.map +1 -0
  125. package/dist/request-limits.js +57 -0
  126. package/dist/request-limits.js.map +1 -0
  127. package/dist/run-plan.d.ts +32 -0
  128. package/dist/run-plan.d.ts.map +1 -0
  129. package/dist/run-plan.js +43 -0
  130. package/dist/run-plan.js.map +1 -0
  131. package/dist/run-policy.d.ts +32 -0
  132. package/dist/run-policy.d.ts.map +1 -0
  133. package/dist/run-policy.js +37 -0
  134. package/dist/run-policy.js.map +1 -0
  135. package/dist/secret-policy.d.ts +13 -0
  136. package/dist/secret-policy.d.ts.map +1 -0
  137. package/dist/secret-policy.js +69 -0
  138. package/dist/secret-policy.js.map +1 -0
  139. package/dist/settings-layering.d.ts +45 -0
  140. package/dist/settings-layering.d.ts.map +1 -0
  141. package/dist/settings-layering.js +134 -0
  142. package/dist/settings-layering.js.map +1 -0
  143. package/dist/settings.d.ts +238 -0
  144. package/dist/settings.d.ts.map +1 -0
  145. package/dist/settings.js +378 -0
  146. package/dist/settings.js.map +1 -0
  147. package/dist/skill-manifest.d.ts +18 -0
  148. package/dist/skill-manifest.d.ts.map +1 -0
  149. package/dist/skill-manifest.js +125 -0
  150. package/dist/skill-manifest.js.map +1 -0
  151. package/dist/source-text.d.ts +83 -0
  152. package/dist/source-text.d.ts.map +1 -0
  153. package/dist/source-text.js +310 -0
  154. package/dist/source-text.js.map +1 -0
  155. package/dist/suppression-ceiling.d.ts +48 -0
  156. package/dist/suppression-ceiling.d.ts.map +1 -0
  157. package/dist/suppression-ceiling.js +115 -0
  158. package/dist/suppression-ceiling.js.map +1 -0
  159. package/dist/tailwind-tokens.d.ts +16 -0
  160. package/dist/tailwind-tokens.d.ts.map +1 -0
  161. package/dist/tailwind-tokens.js +70 -0
  162. package/dist/tailwind-tokens.js.map +1 -0
  163. package/dist/text-escapes.d.ts +7 -0
  164. package/dist/text-escapes.d.ts.map +1 -0
  165. package/dist/text-escapes.js +14 -0
  166. package/dist/text-escapes.js.map +1 -0
  167. package/dist/toolchain-pins.d.ts +42 -0
  168. package/dist/toolchain-pins.d.ts.map +1 -0
  169. package/dist/toolchain-pins.js +99 -0
  170. package/dist/toolchain-pins.js.map +1 -0
  171. package/dist/url-safety.d.ts +12 -0
  172. package/dist/url-safety.d.ts.map +1 -0
  173. package/dist/url-safety.js +58 -0
  174. package/dist/url-safety.js.map +1 -0
  175. package/dist/version-policy.d.ts +99 -0
  176. package/dist/version-policy.d.ts.map +1 -0
  177. package/dist/version-policy.js +306 -0
  178. package/dist/version-policy.js.map +1 -0
  179. package/dist/vulnerability-policy.d.ts +66 -0
  180. package/dist/vulnerability-policy.d.ts.map +1 -0
  181. package/dist/vulnerability-policy.js +106 -0
  182. package/dist/vulnerability-policy.js.map +1 -0
  183. package/dist/wiring-policy.d.ts +226 -0
  184. package/dist/wiring-policy.d.ts.map +1 -0
  185. package/dist/wiring-policy.js +458 -0
  186. package/dist/wiring-policy.js.map +1 -0
  187. package/dist/wiring-violation.d.ts +6 -0
  188. package/dist/wiring-violation.d.ts.map +1 -0
  189. package/dist/wiring-violation.js +8 -0
  190. package/dist/wiring-violation.js.map +1 -0
  191. package/dist/workspace-policy.d.ts +177 -0
  192. package/dist/workspace-policy.d.ts.map +1 -0
  193. package/dist/workspace-policy.js +228 -0
  194. package/dist/workspace-policy.js.map +1 -0
  195. package/dist/yaml-blocks.d.ts +43 -0
  196. package/dist/yaml-blocks.d.ts.map +1 -0
  197. package/dist/yaml-blocks.js +87 -0
  198. package/dist/yaml-blocks.js.map +1 -0
  199. package/package.json +41 -0
@@ -0,0 +1,312 @@
1
+ // Managed-file policy. Some configuration is read
2
+ // from the working tree by tools ploaness does not run (git, editors, coding agents), so it cannot live
3
+ // inside the package and be resolved through node_modules the way an ESLint or Biome config can. Those
4
+ // files are materialised into the consumer tree by `ploaness sync` and policed here.
5
+ //
6
+ // Five dispositions:
7
+ // PINNED - ploaness owns the content; the file must exist and match byte for byte.
8
+ // SEED - ploaness writes it once when absent; the project owns it thereafter and may edit it.
9
+ // FORBIDDEN - the path must not exist, because ploaness supplies that configuration itself and a
10
+ // working-tree copy would silently shadow or contradict it.
11
+ // SECTION - ploaness owns a marked block at the top of the file; the project owns everything below
12
+ // it. Neither PINNED nor SEED fits a file both parties must write: pinning it would forbid
13
+ // the project its own agent instructions, and seeding it would let the contract statement
14
+ // drift the moment ploaness changed.
15
+ // REFERENCE - the path need not exist, but if it does it carries nothing but a pointer at AGENTS.md.
16
+ // This is for the entry-point file a coding agent other than Claude reads. PINNED cannot
17
+ // express it: pinning would force a project to carry an entry point for a tool it does
18
+ // not use, and FORBIDDEN would bar it from using that tool at all. What the rule refuses
19
+ // is a second set of instructions - the standard says the root instruction file holds the
20
+ // rules and a tool-specific one only points at it, so two agents cannot be told two
21
+ // different things. The form of the pointer is the tool's own: `@AGENTS.md` is Claude's
22
+ // import syntax and means nothing to Cursor, so the check reads for the NAME rather than
23
+ // demanding one spelling and forcing a file the tool cannot act on.
24
+ const DISPOSITIONS = new Set([
25
+ 'PINNED',
26
+ 'SEED',
27
+ 'FORBIDDEN',
28
+ 'SECTION',
29
+ 'REFERENCE',
30
+ ]);
31
+ // A guard rather than a set membership test followed by an assertion. Both say the same thing; only
32
+ // this one is a claim the compiler checks, so a disposition added to the union without being added to
33
+ // the set above stops compiling instead of parsing into a value no rule handles.
34
+ const isDisposition = (raw) => DISPOSITIONS.has(raw);
35
+ const SCOPES = new Set([
36
+ 'REPOSITORY',
37
+ 'APPLICATION',
38
+ 'PAYLOAD',
39
+ 'EVERYWHERE',
40
+ ]);
41
+ const isScope = (raw) => SCOPES.has(raw);
42
+ // A manifest row is a path, a disposition and a scope, and nothing else. A row missing the third column
43
+ // is malformed rather than defaulted: a managed file placed in the wrong half of a workspace is either
44
+ // demanded where it cannot be or ignored where it must be, and neither should be reachable by omission.
45
+ const MANIFEST_COLUMNS = 3;
46
+ /** Opens the block ploaness owns inside a SECTION file. */
47
+ export const SECTION_BEGIN = '<!-- BEGIN PLOANESS MANAGED INSTRUCTIONS -->';
48
+ /** Closes the block ploaness owns inside a SECTION file. */
49
+ export const SECTION_END = '<!-- END PLOANESS MANAGED INSTRUCTIONS -->';
50
+ const occurrences = (content, marker) => content.split(marker).length - 1;
51
+ /**
52
+ * Read back the ploaness-owned block, markers included.
53
+ *
54
+ * The markers are HTML comments so they survive in every Markdown renderer without being visible to a
55
+ * human reader, and so an agent editing the file below them can see exactly where its own territory
56
+ * starts. The block must lead the file and appear exactly once: a contract an agent reaches only after
57
+ * scrolling past the project's own prose is not the first thing it reads, and a second copy would let
58
+ * one block drift while the other stayed current.
59
+ * @param rawContent the whole file as it currently stands in the working tree.
60
+ * @returns which of the three states the file is in.
61
+ */
62
+ export const readManagedSection = (rawContent) => {
63
+ // Line endings are normalised before the markers are looked for. The begin marker is required to be
64
+ // followed by `\n`, so on a CRLF checkout every SECTION file reported "the managed block does not
65
+ // begin the file" and told the reader to repair markers that were already correct.
66
+ const content = rawContent.replaceAll('\r\n', '\n');
67
+ const begins = occurrences(content, SECTION_BEGIN);
68
+ const ends = occurrences(content, SECTION_END);
69
+ if (begins === 0 && ends === 0) {
70
+ return { kind: 'absent' };
71
+ }
72
+ if (begins !== 1 || ends !== 1) {
73
+ return {
74
+ kind: 'malformed',
75
+ reason: 'the managed markers appear more than once, or one is missing',
76
+ };
77
+ }
78
+ // Requiring the block to lead the file also settles marker order: with one of each marker and the
79
+ // begin marker at offset zero, the end marker can only follow it.
80
+ if (!content.startsWith(`${SECTION_BEGIN}\n`)) {
81
+ return { kind: 'malformed', reason: 'the managed block does not begin the file' };
82
+ }
83
+ const end = content.indexOf(SECTION_END);
84
+ return { kind: 'present', block: content.slice(0, end + SECTION_END.length) };
85
+ };
86
+ /**
87
+ * Put the ploaness-owned block into a file, preserving everything the project owns.
88
+ *
89
+ * An existing block is replaced where it stands, so the project text below it is carried through
90
+ * byte for byte. A file with no block gets one at the very top, because the contract has to be the
91
+ * first thing an agent reads.
92
+ * @param rawContent the current file, or the empty string when it does not exist yet.
93
+ * @param block the block ploaness ships, markers included.
94
+ * @returns the spliced file, or undefined when the markers are too ambiguous to edit safely.
95
+ */
96
+ export const applyManagedSection = (rawContent, block) => {
97
+ // Normalised here as well, and for a sharper reason than in the reader: the block length below is
98
+ // measured on the normalised text, so slicing the raw one would cut at the wrong offset by exactly
99
+ // the number of carriage returns the block contained.
100
+ const content = rawContent.replaceAll('\r\n', '\n');
101
+ const state = readManagedSection(content);
102
+ if (state.kind === 'malformed') {
103
+ return undefined;
104
+ }
105
+ if (state.kind === 'present') {
106
+ return block + content.slice(state.block.length);
107
+ }
108
+ return content.trim().length === 0 ? `${block}\n` : `${block}\n\n${content.replace(/^\n+/, '')}`;
109
+ };
110
+ const readManifestRow = (index, line) => {
111
+ const trimmed = line.trim();
112
+ if (trimmed.length === 0 || trimmed.startsWith('#')) {
113
+ return { asset: undefined, problem: undefined };
114
+ }
115
+ const [path, disposition, scope] = trimmed.split('\t', MANIFEST_COLUMNS);
116
+ if (path === undefined ||
117
+ disposition === undefined ||
118
+ scope === undefined ||
119
+ !isDisposition(disposition) ||
120
+ !isScope(scope)) {
121
+ return {
122
+ asset: undefined,
123
+ // Rendered from the sets rather than written out. The list was spelled a third time here and had
124
+ // already drifted: REFERENCE was missing, so a typo in a REFERENCE row produced advice that
125
+ // contradicted the set the parser actually accepts.
126
+ problem: `manifest line ${String(index + 1)}: expected "<path>", a tab, one of ` +
127
+ `${[...DISPOSITIONS].join(', ')}, a tab, then one of ${[...SCOPES].join(', ')}`,
128
+ };
129
+ }
130
+ return { asset: { path, disposition, scope }, problem: undefined };
131
+ };
132
+ /**
133
+ * Parse a manifest into catalogue entries. Blank lines and `#` comments are ignored; a row is a
134
+ * repo-relative path, a tab, and a disposition. A malformed row is reported rather than silently
135
+ * skipped, so a packaging mistake cannot quietly shrink the catalogue.
136
+ * @param manifest the raw manifest.tsv contents.
137
+ * @returns the parsed entries and any malformed rows.
138
+ */
139
+ export const parseManifest = (manifest) => {
140
+ // Each row is read once into an outcome, then the outcomes are partitioned. A row that is neither an
141
+ // entry nor a problem is a comment or a blank line, and simply yields nothing.
142
+ const rows = [...manifest.split('\n').entries()]
143
+ .map(([index, line]) => readManifestRow(index, line))
144
+ .filter((row) => row.asset !== undefined || row.problem !== undefined);
145
+ return {
146
+ assets: rows.flatMap((row) => row.asset === undefined ? [] : [row.asset]),
147
+ problems: rows.flatMap((row) => row.problem === undefined ? [] : [row.problem]),
148
+ };
149
+ };
150
+ /**
151
+ * Judge a path whose marked block ploaness owns and whose remaining text the project owns.
152
+ * @param asset the catalogue entry.
153
+ * @param state the working-tree and shipped content for that path.
154
+ * @returns a violation, or undefined when the block matches what ploaness ships.
155
+ */
156
+ const checkSection = (asset, state) => {
157
+ const section = readManagedSection(state.actual ?? '');
158
+ // Malformed markers get their own advice on purpose: `ploaness sync` refuses such a file, so telling
159
+ // the project to run it would send it round a loop that cannot terminate.
160
+ if (section.kind === 'malformed') {
161
+ return { path: asset.path, reason: `${section.reason}; repair the markers by hand` };
162
+ }
163
+ if (section.kind === 'absent') {
164
+ return {
165
+ path: asset.path,
166
+ reason: 'the ploaness managed block is missing; run `ploaness sync`',
167
+ };
168
+ }
169
+ return section.block === (state.expected ?? '').trim()
170
+ ? undefined
171
+ : {
172
+ path: asset.path,
173
+ reason: 'the ploaness managed block drifted from the ploaness copy; run `ploaness sync`',
174
+ };
175
+ };
176
+ // One checker per disposition, rather than a chain of tests inside `checkAsset`. Five dispositions
177
+ // carried that chain past the complexity cap, and the cap is never raised - so the shape changed. Each
178
+ // checker decides the absent case for itself, because that is precisely where the dispositions differ:
179
+ // a missing PINNED file is a defect, a missing REFERENCE file is a project that uses one agent.
180
+ const missing = (asset) => ({
181
+ path: asset.path,
182
+ reason: 'managed file is missing; run `ploaness sync`',
183
+ });
184
+ const checkForbidden = (asset, state) => state.isPresent
185
+ ? {
186
+ path: asset.path,
187
+ reason: 'ploaness supplies this configuration, so a working-tree copy shadows it; delete the file',
188
+ }
189
+ : undefined;
190
+ const checkSeed = (asset, state) => state.isPresent ? undefined : missing(asset);
191
+ const checkPinned = (asset, state) => {
192
+ if (!state.isPresent) {
193
+ return missing(asset);
194
+ }
195
+ return state.actual === state.expected
196
+ ? undefined
197
+ : {
198
+ path: asset.path,
199
+ reason: 'managed file drifted from the ploaness copy; run `ploaness sync`',
200
+ };
201
+ };
202
+ /** The instruction file every other one points at. */
203
+ const ROOT_INSTRUCTION_FILE = 'AGENTS.md';
204
+ // Every line that says anything must say the same thing: read AGENTS.md. A line that does not name it
205
+ // is this file stating a rule of its own, which is what makes two agents answerable to two contracts.
206
+ const checkReference = (asset, state) => {
207
+ // Optional, unlike every other disposition: a project that uses no coding agent but Claude carries no
208
+ // entry point for one, and requiring the file would be ploaness deciding which tools it uses.
209
+ if (!state.isPresent) {
210
+ return undefined;
211
+ }
212
+ const stated = (state.actual ?? '')
213
+ .split('\n')
214
+ .map((line) => line.trim())
215
+ .filter((line) => line.length > 0);
216
+ if (stated.length === 0) {
217
+ return {
218
+ path: asset.path,
219
+ reason: `is empty; a tool-specific instruction file points at ${ROOT_INSTRUCTION_FILE}`,
220
+ };
221
+ }
222
+ return stated.every((line) => line.includes(ROOT_INSTRUCTION_FILE))
223
+ ? undefined
224
+ : {
225
+ path: asset.path,
226
+ reason: `states instructions of its own; a tool-specific instruction file carries nothing but a ` +
227
+ `reference to ${ROOT_INSTRUCTION_FILE}, so no two agents are held to two contracts`,
228
+ };
229
+ };
230
+ const CHECKERS = {
231
+ FORBIDDEN: checkForbidden,
232
+ PINNED: checkPinned,
233
+ REFERENCE: checkReference,
234
+ SECTION: (asset, state) => state.isPresent ? checkSection(asset, state) : missing(asset),
235
+ SEED: checkSeed,
236
+ };
237
+ /**
238
+ * Judge one managed path against its disposition.
239
+ * @param asset the catalogue entry.
240
+ * @param state the working-tree facts for that path.
241
+ * @returns a violation, or undefined when the path conforms.
242
+ */
243
+ export const checkAsset = (asset, state) => CHECKERS[asset.disposition](asset, state);
244
+ /**
245
+ * Judge every catalogue entry the project has not explicitly taken over.
246
+ * @param assets the parsed catalogue.
247
+ * @param unmanaged repo-relative paths the project owns instead.
248
+ * @param stateOf resolves the working-tree and shipped content for a path.
249
+ * @returns one violation per non-conforming path; empty means the tree matches the catalogue.
250
+ */
251
+ export const findAssetViolations = (assets, unmanaged, stateOf) => {
252
+ const owned = new Set(unmanaged);
253
+ return assets.flatMap((asset) => {
254
+ if (owned.has(asset.path)) {
255
+ return [];
256
+ }
257
+ const violation = checkAsset(asset, stateOf(asset.path));
258
+ return violation === undefined ? [] : [violation];
259
+ });
260
+ };
261
+ /**
262
+ * Decide the sync action for one path. A PINNED file is always rewritten, so drift is repaired; a SEED
263
+ * file is written only when absent, so the project's own edits survive; a FORBIDDEN file is deleted; a
264
+ * SECTION file has only its marked block replaced.
265
+ * @param asset the catalogue entry.
266
+ * @param isPresent whether the path is currently present in the working tree.
267
+ * @returns the action `ploaness sync` performs.
268
+ */
269
+ export const syncAction = (asset, isPresent) => {
270
+ if (asset.disposition === 'FORBIDDEN') {
271
+ return isPresent ? 'delete' : 'skip';
272
+ }
273
+ // Never written, in either state. Creating one would hand the project an entry point for a tool it
274
+ // may not use, and rewriting one would replace a pointer the tool understands with a guess.
275
+ if (asset.disposition === 'REFERENCE') {
276
+ return 'skip';
277
+ }
278
+ if (asset.disposition === 'SEED') {
279
+ return isPresent ? 'skip' : 'write';
280
+ }
281
+ // A SECTION file is never overwritten, even when absent: the project text below the block is the
282
+ // project's, and creating the file wholesale would be ploaness writing that text on its behalf.
283
+ if (asset.disposition === 'SECTION') {
284
+ return 'splice';
285
+ }
286
+ return 'write';
287
+ };
288
+ /**
289
+ * The catalogue entries that apply at the repository root.
290
+ * @param assets the whole catalogue.
291
+ * @returns the repository-scope entries and the forbidden paths, which apply everywhere.
292
+ */
293
+ export const repositoryAssets = (assets) => assets.filter((asset) => asset.scope === 'REPOSITORY' || asset.scope === 'EVERYWHERE');
294
+ /**
295
+ * The catalogue entries that apply inside one member.
296
+ *
297
+ * A library receives only the forbidden paths: it has no server to sweep, so a spec that drives one
298
+ * would be a managed file demanding a browser the package was never going to open.
299
+ * @param assets the whole catalogue.
300
+ * @param host what the member can hold, derived from its kind.
301
+ * @returns the entries that apply to that member.
302
+ */
303
+ export const memberAssets = (assets, host) => assets.filter((asset) => {
304
+ if (asset.scope === 'EVERYWHERE') {
305
+ return true;
306
+ }
307
+ if (host.isPayload && asset.scope === 'PAYLOAD') {
308
+ return true;
309
+ }
310
+ return host.hasRuntime && asset.scope === 'APPLICATION';
311
+ });
312
+ //# sourceMappingURL=asset-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-policy.js","sourceRoot":"","sources":["../src/asset-policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export interface TypographyViolation {
2
+ readonly line: number;
3
+ readonly column: number;
4
+ readonly label: string;
5
+ readonly replacement: string;
6
+ }
7
+ /**
8
+ * Scans text for banned AI-typography, reporting 1-based line and column positions.
9
+ * @param text the content to scan, with lines separated by "\n" or "\r\n".
10
+ * @returns one violation per banned character found, in reading order.
11
+ */
12
+ export declare const findTypographyViolations: (text: string) => readonly TypographyViolation[];
13
+ //# sourceMappingURL=banned-typography.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banned-typography.d.ts","sourceRoot":"","sources":["../src/banned-typography.ts"],"names":[],"mappings":"AAmDA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAiCD;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,MAAM,KAAG,SAAS,mBAAmB,EAM/E,CAAA"}
@@ -0,0 +1,78 @@
1
+ // Shared AI-typography ban: characters a human rarely types but language models love, each mapped to
2
+ // the plain ASCII a human would actually write. One source of truth, consumed by both the file gate
3
+ // (`conventions`, in packages/cli/src/checks/conventions.ts) and the history gate (`commit-history`, in
4
+ // packages/cli/src/checks/history.ts). Every banned character is
5
+ // referenced via its code point only, so neither this module nor its callers trip the check on
6
+ // themselves.
7
+ const STRAIGHT_DOUBLE_QUOTE = 'a straight double quote';
8
+ const PLAIN_HYPHEN = 'a hyphen "-"';
9
+ // The code points are named rather than written at each use, because a bare number in the table below
10
+ // would be exactly the unexplained literal the standard bans - and this module cannot spell the
11
+ // characters out, since its own source is scanned by the rule it implements.
12
+ const EM_DASH = 0x20_14;
13
+ const EN_DASH = 0x20_13;
14
+ const ELLIPSIS = 0x20_26;
15
+ const LEFT_DOUBLE_QUOTE = 0x20_1c;
16
+ const RIGHT_DOUBLE_QUOTE = 0x20_1d;
17
+ const LOW_DOUBLE_QUOTE = 0x20_1e;
18
+ const BANNED_CHARACTERS = [
19
+ { char: String.fromCodePoint(EM_DASH), label: 'em dash (U+2014)', replacement: PLAIN_HYPHEN },
20
+ { char: String.fromCodePoint(EN_DASH), label: 'en dash (U+2013)', replacement: PLAIN_HYPHEN },
21
+ {
22
+ char: String.fromCodePoint(ELLIPSIS),
23
+ label: 'ellipsis (U+2026)',
24
+ replacement: 'three dots "..."',
25
+ },
26
+ {
27
+ char: String.fromCodePoint(LEFT_DOUBLE_QUOTE),
28
+ label: 'left double quote (U+201C)',
29
+ replacement: STRAIGHT_DOUBLE_QUOTE,
30
+ },
31
+ {
32
+ char: String.fromCodePoint(RIGHT_DOUBLE_QUOTE),
33
+ label: 'right double quote (U+201D)',
34
+ replacement: STRAIGHT_DOUBLE_QUOTE,
35
+ },
36
+ {
37
+ char: String.fromCodePoint(LOW_DOUBLE_QUOTE),
38
+ label: 'low double quote (U+201E)',
39
+ replacement: STRAIGHT_DOUBLE_QUOTE,
40
+ },
41
+ ];
42
+ const BY_CHARACTER = new Map(BANNED_CHARACTERS.map((banned) => [
43
+ banned.char,
44
+ banned,
45
+ ]));
46
+ // Every occurrence, at its own column, in the order it is read.
47
+ //
48
+ // This used to run `indexOf` once per banned character, which reported only the FIRST of each on a
49
+ // line - so a line with two em dashes produced one finding, and clearing a file took as many runs as
50
+ // it had repeats. Walking the line by code point fixes the count and the column together: `indexOf`
51
+ // returns a UTF-16 offset, so a column after an emoji named a position the editor does not have.
52
+ const violationsInLine = (line, lineNumber) =>
53
+ // Code points are the unit wanted here: a banned character is one code point, and a column counted
54
+ // in code units names a position no editor agrees with.
55
+ // eslint-disable-next-line @typescript-eslint/no-misused-spread -- see the note above
56
+ [...line].flatMap((character, index) => {
57
+ const banned = BY_CHARACTER.get(character);
58
+ return banned === undefined
59
+ ? []
60
+ : [
61
+ {
62
+ line: lineNumber,
63
+ column: index + 1,
64
+ label: banned.label,
65
+ replacement: banned.replacement,
66
+ },
67
+ ];
68
+ });
69
+ /**
70
+ * Scans text for banned AI-typography, reporting 1-based line and column positions.
71
+ * @param text the content to scan, with lines separated by "\n" or "\r\n".
72
+ * @returns one violation per banned character found, in reading order.
73
+ */
74
+ export const findTypographyViolations = (text) => text
75
+ .replaceAll('\r\n', '\n')
76
+ .split('\n')
77
+ .flatMap((line, index) => violationsInLine(line, index + 1));
78
+ //# sourceMappingURL=banned-typography.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banned-typography.js","sourceRoot":"","sources":["../src/banned-typography.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,oGAAoG;AACpG,wGAAwG;AACxG,iEAAiE;AACjE,+FAA+F;AAC/F,cAAc;AAQd,MAAM,qBAAqB,GAAW,yBAAyB,CAAA;AAC/D,MAAM,YAAY,GAAW,cAAc,CAAA;AAE3C,sGAAsG;AACtG,gGAAgG;AAChG,6EAA6E;AAC7E,MAAM,OAAO,GAAW,OAAO,CAAA;AAC/B,MAAM,OAAO,GAAW,OAAO,CAAA;AAC/B,MAAM,QAAQ,GAAW,OAAO,CAAA;AAChC,MAAM,iBAAiB,GAAW,OAAO,CAAA;AACzC,MAAM,kBAAkB,GAAW,OAAO,CAAA;AAC1C,MAAM,gBAAgB,GAAW,OAAO,CAAA;AAExC,MAAM,iBAAiB,GAA+B;IACpD,EAAE,IAAI,EAAE,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,YAAY,EAAE;IAC7F,EAAE,IAAI,EAAE,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,YAAY,EAAE;IAC7F;QACE,IAAI,EAAE,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC;QACpC,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EAAE,kBAAkB;KAChC;IACD;QACE,IAAI,EAAE,MAAM,CAAC,aAAa,CAAC,iBAAiB,CAAC;QAC7C,KAAK,EAAE,4BAA4B;QACnC,WAAW,EAAE,qBAAqB;KACnC;IACD;QACE,IAAI,EAAE,MAAM,CAAC,aAAa,CAAC,kBAAkB,CAAC;QAC9C,KAAK,EAAE,6BAA6B;QACpC,WAAW,EAAE,qBAAqB;KACnC;IACD;QACE,IAAI,EAAE,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC;QAC5C,KAAK,EAAE,2BAA2B;QAClC,WAAW,EAAE,qBAAqB;KACnC;CACF,CAAA;AASD,MAAM,YAAY,GAAyC,IAAI,GAAG,CAChE,iBAAiB,CAAC,GAAG,CAAC,CAAC,MAAuB,EAAsC,EAAE,CAAC;IACrF,MAAM,CAAC,IAAI;IACX,MAAM;CACP,CAAC,CACH,CAAA;AAED,gEAAgE;AAChE,EAAE;AACF,mGAAmG;AACnG,qGAAqG;AACrG,oGAAoG;AACpG,iGAAiG;AACjG,MAAM,gBAAgB,GAAG,CAAC,IAAY,EAAE,UAAkB,EAAkC,EAAE;AAC5F,mGAAmG;AACnG,wDAAwD;AACxD,sFAAsF;AACtF,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,SAAiB,EAAE,KAAa,EAAkC,EAAE;IACrF,MAAM,MAAM,GAAgC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACvE,OAAO,MAAM,KAAK,SAAS;QACzB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC;YACE;gBACE,IAAI,EAAE,UAAU;gBAChB,MAAM,EAAE,KAAK,GAAG,CAAC;gBACjB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,WAAW,EAAE,MAAM,CAAC,WAAW;aAChC;SACF,CAAA;AACP,CAAC,CAAC,CAAA;AAEJ;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,IAAY,EAAkC,EAAE,CACvF,IAAI;KACD,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC;KACxB,KAAK,CAAC,IAAI,CAAC;KACX,OAAO,CAAC,CAAC,IAAY,EAAE,KAAa,EAAkC,EAAE,CACvE,gBAAgB,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAClC,CAAA"}
@@ -0,0 +1,16 @@
1
+ /** One built client asset and its gzipped size. */
2
+ export interface BundleFile {
3
+ readonly path: string;
4
+ readonly gzipBytes: number;
5
+ }
6
+ /** The outcome of comparing the measured client JS against the budget. */
7
+ export interface BundleReport {
8
+ readonly totalGzipBytes: number;
9
+ readonly budgetBytes: number;
10
+ readonly isWithinBudget: boolean;
11
+ readonly fileCount: number;
12
+ }
13
+ export declare const BUNDLE_BUDGET_BYTES: number;
14
+ /** Sum the gzipped sizes of the built assets and compare the total against the budget. */
15
+ export declare const evaluateBundle: (files: readonly BundleFile[], budgetBytes: number) => BundleReport;
16
+ //# sourceMappingURL=bundle-budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle-budget.d.ts","sourceRoot":"","sources":["../src/bundle-budget.ts"],"names":[],"mappings":"AAUA,mDAAmD;AACnD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAkBD,eAAO,MAAM,mBAAmB,EAAE,MAAmC,CAAA;AAErE,0FAA0F;AAC1F,eAAO,MAAM,cAAc,GAAI,OAAO,SAAS,UAAU,EAAE,EAAE,aAAa,MAAM,KAAG,YAWlF,CAAA"}
@@ -0,0 +1,37 @@
1
+ // Bundle-size budget policy: the pure logic - the byte ceiling and the sum-vs-budget evaluation - lives
2
+ // here so it is unit-tested; the `bundle` gate in packages/cli/src/checks/integrity.ts walks
3
+ // `.next/static`, gzips each chunk, and reports.
4
+ //
5
+ // Why this is a DETERMINISTIC gate (unlike Lighthouse, which this repo deliberately omits): gzipped
6
+ // byte counts of built JS are a fixed function of the source + pinned dependency and Node versions, so
7
+ // the number changes only when your change changes the bundle - a true signal, never environment flake.
8
+ // It measures the TOTAL client JS (the whole `.next/static` tree), so it catches a catastrophic
9
+ // regression (an accidental heavyweight dependency) rather than a per-route First-Load delta.
10
+ // Total gzipped client-JS ceiling. This is a CONSCIOUS ceiling: raise it deliberately, in a reviewed
11
+ // commit, when a real application needs the room - the way the licence allowlist is widened on purpose,
12
+ // never to silence a regression. A project cannot raise it, only lower it, because `readSettings` takes
13
+ // `Math.min` over the declared value.
14
+ //
15
+ // It was 900 KiB, calibrated against one production build at ~768 KiB with headroom for the Payload
16
+ // admin bundle to grow. A second real Payload application measured 954.6 KiB - and roughly 680 KiB of
17
+ // that was `@payloadcms/ui`, Lexical and the datepicker Payload pulls in, before the project wrote a
18
+ // line of its own. The ceiling was therefore below what the framework this harness exists to govern
19
+ // ships on its own, which made it unreachable rather than demanding: no amount of work on the project's
20
+ // code would have brought it under.
21
+ //
22
+ // The gate is not exercised by `it/`, which builds no application, so a number here is only ever as
23
+ // good as the applications it has been checked against. It has now been checked against two.
24
+ const BYTES_PER_KIB = 1024;
25
+ const BUDGET_KIB = 1100;
26
+ export const BUNDLE_BUDGET_BYTES = BUDGET_KIB * BYTES_PER_KIB;
27
+ /** Sum the gzipped sizes of the built assets and compare the total against the budget. */
28
+ export const evaluateBundle = (files, budgetBytes) => {
29
+ const totalGzipBytes = files.reduce((sum, file) => sum + file.gzipBytes, 0);
30
+ return {
31
+ totalGzipBytes,
32
+ budgetBytes,
33
+ isWithinBudget: totalGzipBytes <= budgetBytes,
34
+ fileCount: files.length,
35
+ };
36
+ };
37
+ //# sourceMappingURL=bundle-budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle-budget.js","sourceRoot":"","sources":["../src/bundle-budget.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,6FAA6F;AAC7F,iDAAiD;AACjD,EAAE;AACF,oGAAoG;AACpG,uGAAuG;AACvG,wGAAwG;AACxG,gGAAgG;AAChG,8FAA8F;AAgB9F,qGAAqG;AACrG,wGAAwG;AACxG,wGAAwG;AACxG,sCAAsC;AACtC,EAAE;AACF,oGAAoG;AACpG,sGAAsG;AACtG,qGAAqG;AACrG,oGAAoG;AACpG,wGAAwG;AACxG,oCAAoC;AACpC,EAAE;AACF,oGAAoG;AACpG,6FAA6F;AAC7F,MAAM,aAAa,GAAW,IAAI,CAAA;AAClC,MAAM,UAAU,GAAW,IAAI,CAAA;AAC/B,MAAM,CAAC,MAAM,mBAAmB,GAAW,UAAU,GAAG,aAAa,CAAA;AAErE,0FAA0F;AAC1F,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAA4B,EAAE,WAAmB,EAAgB,EAAE;IAChG,MAAM,cAAc,GAAW,KAAK,CAAC,MAAM,CACzC,CAAC,GAAW,EAAE,IAAgB,EAAU,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,SAAS,EAC/D,CAAC,CACF,CAAA;IACD,OAAO;QACL,cAAc;QACd,WAAW;QACX,cAAc,EAAE,cAAc,IAAI,WAAW;QAC7C,SAAS,EAAE,KAAK,CAAC,MAAM;KACxB,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,37 @@
1
+ export interface ParsedMessage {
2
+ readonly header: string;
3
+ readonly body: string;
4
+ }
5
+ export interface DiffStat {
6
+ readonly files: number;
7
+ readonly lines: number;
8
+ }
9
+ /**
10
+ * Splits a raw commit message into header and body, dropping git comment lines and any verbose-diff
11
+ * section below the scissors marker.
12
+ * @param raw the full commit-message file contents.
13
+ * @returns the first non-blank line as `header` and the remaining prose as `body` (both may be empty).
14
+ */
15
+ export declare const parseMessage: (raw: string) => ParsedMessage;
16
+ /**
17
+ * Validates a parsed commit message, returning a human-readable problem per rule violation. Every
18
+ * commit is held to every rule: there is no exemption list, so a git-generated merge, revert, or
19
+ * autosquash subject must be rewritten by hand to conform.
20
+ * @param message the header/body pair from {@link parseMessage}.
21
+ * @param isBodyRequired whether the change is non-trivial and therefore must carry an explanatory body.
22
+ * @returns an empty array when the message passes.
23
+ */
24
+ export declare const validateMessage: (message: ParsedMessage, isBodyRequired: boolean) => readonly string[];
25
+ /**
26
+ * Parses `git --numstat` output into a file count and a total changed-line count.
27
+ * @param numstat the raw stdout of a `--numstat` diff (tab-separated added/deleted/path rows).
28
+ * @returns the number of changed files and the sum of added plus deleted lines (binary rows count 0).
29
+ */
30
+ export declare const parseNumstat: (numstat: string) => DiffStat;
31
+ /**
32
+ * Decides whether a diff is large enough to require an explanatory commit body.
33
+ * @param stat the file and line counts from {@link parseNumstat}.
34
+ * @returns true when the change touches more than 2 files or more than 50 lines.
35
+ */
36
+ export declare const isNonTrivial: (stat: DiffStat) => boolean;
37
+ //# sourceMappingURL=commit-message.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commit-message.d.ts","sourceRoot":"","sources":["../src/commit-message.ts"],"names":[],"mappings":"AA4CA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,aAmB1C,CAAA;AAgED;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,aAAa,EACtB,gBAAgB,OAAO,KACtB,SAAS,MAAM,EAMjB,CAAA;AAOD;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,KAAG,QAS9C,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,QAAQ,KAAG,OACoB,CAAA"}