finitemonkey 0.1.0-next.4 → 0.1.0-next.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/finitemonkey.mjs +1325 -146
- package/package.json +1 -1
package/dist/finitemonkey.mjs
CHANGED
|
@@ -583,10 +583,10 @@ var needsPersonProblem = (messageKey, fix, params2 = {}) => ({
|
|
|
583
583
|
});
|
|
584
584
|
var FmError = class extends Error {
|
|
585
585
|
problem;
|
|
586
|
-
constructor(
|
|
587
|
-
super(
|
|
586
|
+
constructor(problem4) {
|
|
587
|
+
super(problem4.code);
|
|
588
588
|
this.name = "FmError";
|
|
589
|
-
this.problem =
|
|
589
|
+
this.problem = problem4;
|
|
590
590
|
}
|
|
591
591
|
};
|
|
592
592
|
|
|
@@ -657,11 +657,11 @@ var success = (data, human2 = []) => ({
|
|
|
657
657
|
data,
|
|
658
658
|
human: human2
|
|
659
659
|
});
|
|
660
|
-
var failureFrom = (t,
|
|
660
|
+
var failureFrom = (t, problem4, human2) => ({
|
|
661
661
|
ok: false,
|
|
662
|
-
exitCode:
|
|
663
|
-
error: { code:
|
|
664
|
-
...
|
|
662
|
+
exitCode: problem4.exitCode,
|
|
663
|
+
error: { code: problem4.code, message: t(problem4.messageKey, problem4.params), fix: problem4.fix },
|
|
664
|
+
...problem4.data === void 0 ? {} : { data: problem4.data },
|
|
665
665
|
...human2 === void 0 ? {} : { human: human2 }
|
|
666
666
|
});
|
|
667
667
|
var toEnvelope = (command, result) => ({
|
|
@@ -1883,6 +1883,7 @@ var implicitContract = (files) => {
|
|
|
1883
1883
|
...hasTests ? [] : [{ id: "tests", kind: "tests" }],
|
|
1884
1884
|
{ id: "placeholders", kind: "placeholders" },
|
|
1885
1885
|
{ id: "secrets", kind: "secrets" },
|
|
1886
|
+
{ id: "prose", kind: "prose" },
|
|
1886
1887
|
{ id: "pr", kind: "pr", requireOpen: false }
|
|
1887
1888
|
],
|
|
1888
1889
|
decisionsNeeded: [],
|
|
@@ -1905,7 +1906,8 @@ var CHECK_KINDS = [
|
|
|
1905
1906
|
"secrets",
|
|
1906
1907
|
"worktree",
|
|
1907
1908
|
"merge",
|
|
1908
|
-
"prod"
|
|
1909
|
+
"prod",
|
|
1910
|
+
"prose"
|
|
1909
1911
|
];
|
|
1910
1912
|
var GIT_REQUIREMENTS = ["committed", "pushed", "pr_open"];
|
|
1911
1913
|
var SIZES = ["S", "M", "L"];
|
|
@@ -2153,8 +2155,8 @@ var fetchView = (runtime, git, branch) => {
|
|
|
2153
2155
|
var prStatus = (runtime, git, paths, ttlMs = PR_CACHE_TTL_MS) => {
|
|
2154
2156
|
const branch = currentBranch(git);
|
|
2155
2157
|
if (branch === void 0) return { kind: "no_branch" };
|
|
2156
|
-
const
|
|
2157
|
-
if (
|
|
2158
|
+
const hit2 = cached(runtime, paths, branch, ttlMs);
|
|
2159
|
+
if (hit2 !== void 0) return { kind: "found", branch, pr: hit2, localHead: headSha(git) };
|
|
2158
2160
|
const status = fetchView(runtime, git, branch);
|
|
2159
2161
|
if (status.kind !== "fetched") return status;
|
|
2160
2162
|
writeJsonFile(runtime, paths.cache("pr.json"), { fetchedAt: nowMs(runtime), branch, view: status.view });
|
|
@@ -2172,21 +2174,21 @@ var conflictFiles = (git, base) => {
|
|
|
2172
2174
|
|
|
2173
2175
|
// src/gate/prepare.ts
|
|
2174
2176
|
var diskFiles = (runtime, root) => ({
|
|
2175
|
-
read: (
|
|
2176
|
-
const text6 = runtime.readText(toAbsolutePath(join3(root,
|
|
2177
|
+
read: (relative8) => {
|
|
2178
|
+
const text6 = runtime.readText(toAbsolutePath(join3(root, relative8)));
|
|
2177
2179
|
return text6.ok ? text6.value : void 0;
|
|
2178
2180
|
},
|
|
2179
|
-
exists: (
|
|
2181
|
+
exists: (relative8) => runtime.stat(toAbsolutePath(join3(root, relative8))) !== void 0
|
|
2180
2182
|
});
|
|
2181
2183
|
var baseFiles = (git, base, disk) => ({
|
|
2182
|
-
read: (
|
|
2184
|
+
read: (relative8) => base === EMPTY_TREE ? disk.read(relative8) : fileAt(git, base, relative8),
|
|
2183
2185
|
exists: disk.exists
|
|
2184
2186
|
});
|
|
2185
2187
|
var loadContract = (options, paths, git, base) => {
|
|
2186
2188
|
const disk = diskFiles(options.runtime, paths.root);
|
|
2187
2189
|
const id = activeTaskId(options.runtime, paths, options.taskId);
|
|
2188
2190
|
if (id === void 0) return accept(loadImplicit(options.runtime, paths, baseFiles(git, base, disk)));
|
|
2189
|
-
return loadTask(options.runtime, paths, { disk: disk.read, base: (
|
|
2191
|
+
return loadTask(options.runtime, paths, { disk: disk.read, base: (relative8) => fileAt(git, base, relative8) }, id);
|
|
2190
2192
|
};
|
|
2191
2193
|
var memo = (make) => {
|
|
2192
2194
|
let value;
|
|
@@ -2667,7 +2669,47 @@ var en_default = {
|
|
|
2667
2669
|
"gate.secrets.finding": "secret {rule} at {file}:{line}",
|
|
2668
2670
|
"gate.secrets.incident": "secret incident {id} is {state}",
|
|
2669
2671
|
"gate.secrets.clean": "no new secret in the changes and no open incident",
|
|
2670
|
-
"gate.require.secrets": "no new secret and no secret incident left open"
|
|
2672
|
+
"gate.require.secrets": "no new secret and no secret incident left open",
|
|
2673
|
+
"prose.command.check": "Check text for patterns readers recognize as machine-written (local only)",
|
|
2674
|
+
"prose.rule.opener-routine": "open with what happened to someone: a command, a time, an error message",
|
|
2675
|
+
"prose.rule.reassurance": "cut the consolation line; describe the problem and let the reader judge",
|
|
2676
|
+
"prose.rule.slogan-closer": "end on a fact or the next step instead of a slogan",
|
|
2677
|
+
"prose.rule.rhythmic-triplet": "join the short sentences into one that says what each part does",
|
|
2678
|
+
"prose.rule.imperative-fragment": "replace the loose commands with one sentence about what the product does",
|
|
2679
|
+
"prose.rule.antithesis": "say what it is; drop the contrast with what it is not",
|
|
2680
|
+
"prose.rule.abstract-noun": "swap the abstract noun for the scene: who did what, where",
|
|
2681
|
+
"prose.rule.crutch-word": "use the plain word, or a number that proves the claim",
|
|
2682
|
+
"prose.rule.colon-heading": "make the heading the question or the task the reader has",
|
|
2683
|
+
"prose.rule.decorative-emoji": "remove the emoji; the heading carries the meaning",
|
|
2684
|
+
"prose.rule.em-dash-series": "use a period, a comma or parentheses instead of dashes",
|
|
2685
|
+
"prose.target.commit": "The commit message",
|
|
2686
|
+
"prose.target.pr": "The PR text",
|
|
2687
|
+
"prose.target.issue": "The issue text",
|
|
2688
|
+
"prose.span": "lines {start}-{end}: {rules}",
|
|
2689
|
+
"prose.fix_item": "{rule}: {fix}",
|
|
2690
|
+
"prose.warn.command": "{target} reads as machine-written ({spans}). Rewrite it before sending. {fixes}. If it is brand text written on purpose, show the person two alternatives.",
|
|
2691
|
+
"prose.block.command": "blocked: {target} reads as machine-written ({spans}), and this project's preset does not send it. {fixes}. Rewrite it and run the command again.",
|
|
2692
|
+
"prose.block.unreadable": "blocked: could not read {file} to check its text ({detail}). Check the path and run the command again.",
|
|
2693
|
+
"prose.block.marker": "blocked: this would add the marker {marker} to {file}, and only the person can add it. Rewrite the passage instead, or ask the person to add the marker in their own editor.",
|
|
2694
|
+
"prose.check.file": "{file}: {count} passage(s) that read as machine-written",
|
|
2695
|
+
"prose.check.span": " {file}:{start}-{end} score {score}: {rules}",
|
|
2696
|
+
"prose.check.fix": " do instead: {fix}",
|
|
2697
|
+
"prose.check.allowed": "{file}: skipped, the person marked it with {marker}",
|
|
2698
|
+
"prose.check.clean": "{count} file(s) checked, nothing reads as machine-written. The monkey wrote this one itself.",
|
|
2699
|
+
"prose.error.found": "{count} passage(s) read as machine-written",
|
|
2700
|
+
"prose.error.no_input": "nothing to check: pass files, --stdin, or run it inside a git repository to check the changed Markdown files",
|
|
2701
|
+
"prose.error.unreadable": "could not read {file} ({detail})",
|
|
2702
|
+
"prose.block.marker_shell": "blocked: this command writes the marker {marker}, and only the person can add it. Rewrite the passage instead, or ask the person to add the marker in their own editor.",
|
|
2703
|
+
"prose.rule.requester-reference": "state the technical reason for the change; leave out who asked for it",
|
|
2704
|
+
"prose.rule.person-as-reason": "name the property of the change instead of a person",
|
|
2705
|
+
"prose.rule.origin-project": "describe the technical property you copied instead of naming where it came from",
|
|
2706
|
+
"prose.rule.first-person": "write what the change does, in the present tense, without I or you",
|
|
2707
|
+
"prose.rule.padding": "cut the preamble and start with what changed",
|
|
2708
|
+
"gate.require.prose": "changed Markdown does not read as machine-written",
|
|
2709
|
+
"gate.prose.unavailable": "could not read the changed Markdown files (git unavailable)",
|
|
2710
|
+
"gate.prose.finding": "{file}:{line} reads as machine-written ({rules})",
|
|
2711
|
+
"gate.prose.warning": "warning only in this preset: {file}:{line} reads as machine-written ({rules})",
|
|
2712
|
+
"gate.prose.clean": "{count} changed Markdown file(s), none reads as machine-written"
|
|
2671
2713
|
};
|
|
2672
2714
|
|
|
2673
2715
|
// src/i18n/es.json
|
|
@@ -2979,7 +3021,47 @@ var es_default = {
|
|
|
2979
3021
|
"gate.secrets.finding": "secreto {rule} en {file}:{line}",
|
|
2980
3022
|
"gate.secrets.incident": "incidente de secreto {id} está {state}",
|
|
2981
3023
|
"gate.secrets.clean": "ningún secreto nuevo en los cambios y ningún incidente abierto",
|
|
2982
|
-
"gate.require.secrets": "ningún secreto nuevo y ningún incidente de secreto abierto"
|
|
3024
|
+
"gate.require.secrets": "ningún secreto nuevo y ningún incidente de secreto abierto",
|
|
3025
|
+
"prose.command.check": "Revisa si el texto tiene patrones que el lector reconoce como escritura de IA (solo local)",
|
|
3026
|
+
"prose.rule.opener-routine": "empieza con lo que le pasó a alguien: un comando, una hora, un mensaje de error",
|
|
3027
|
+
"prose.rule.reassurance": "quita la frase de consuelo; describe el problema y deja que el lector juzgue",
|
|
3028
|
+
"prose.rule.slogan-closer": "termina con un hecho o con el siguiente paso, sin eslogan",
|
|
3029
|
+
"prose.rule.rhythmic-triplet": "une las frases cortas en una que diga qué hace cada parte",
|
|
3030
|
+
"prose.rule.imperative-fragment": "cambia las órdenes sueltas por una frase sobre lo que hace el producto",
|
|
3031
|
+
"prose.rule.antithesis": "di lo que es y quita el contraste con lo que no es",
|
|
3032
|
+
"prose.rule.abstract-noun": "cambia el sustantivo abstracto por la escena: quién hizo qué, dónde",
|
|
3033
|
+
"prose.rule.crutch-word": "usa la palabra simple, o un número que pruebe lo que dices",
|
|
3034
|
+
"prose.rule.colon-heading": "haz del título la pregunta o la tarea de quien lee",
|
|
3035
|
+
"prose.rule.decorative-emoji": "quita el emoji; el título ya lleva el sentido",
|
|
3036
|
+
"prose.rule.em-dash-series": "usa punto, coma o paréntesis en lugar de las rayas",
|
|
3037
|
+
"prose.target.commit": "El mensaje de commit",
|
|
3038
|
+
"prose.target.pr": "El texto del PR",
|
|
3039
|
+
"prose.target.issue": "El texto de la issue",
|
|
3040
|
+
"prose.span": "líneas {start}-{end}: {rules}",
|
|
3041
|
+
"prose.fix_item": "{rule}: {fix}",
|
|
3042
|
+
"prose.warn.command": "{target} parece escrito por IA ({spans}). Reescríbelo antes de enviarlo. {fixes}. Si es texto de marca escrito a propósito, muéstrale dos alternativas a la persona.",
|
|
3043
|
+
"prose.block.command": "bloqueado: {target} parece escrito por IA ({spans}), y el preset de este proyecto no deja enviarlo. {fixes}. Reescríbelo y vuelve a ejecutar el comando.",
|
|
3044
|
+
"prose.block.unreadable": "bloqueado: no se pudo leer {file} para revisar el texto ({detail}). Revisa la ruta y vuelve a ejecutar el comando.",
|
|
3045
|
+
"prose.block.marker": "bloqueado: esto añadiría el marcador {marker} en {file}, y solo la persona puede ponerlo. Reescribe el pasaje, o pide a la persona que ponga el marcador en su propio editor.",
|
|
3046
|
+
"prose.check.file": "{file}: {count} pasaje(s) que parecen escritos por IA",
|
|
3047
|
+
"prose.check.span": " {file}:{start}-{end} puntos {score}: {rules}",
|
|
3048
|
+
"prose.check.fix": " haz en su lugar: {fix}",
|
|
3049
|
+
"prose.check.allowed": "{file}: omitido, la persona lo marcó con {marker}",
|
|
3050
|
+
"prose.check.clean": "{count} archivo(s) revisado(s), nada parece escrito por IA. Este lo escribió el mono solo.",
|
|
3051
|
+
"prose.error.found": "{count} pasaje(s) parecen escritos por IA",
|
|
3052
|
+
"prose.error.no_input": "nada que revisar: pasa archivos, --stdin, o ejecútalo dentro de un repositorio git para revisar los Markdown modificados",
|
|
3053
|
+
"prose.error.unreadable": "no se pudo leer {file} ({detail})",
|
|
3054
|
+
"prose.block.marker_shell": "bloqueado: este comando escribe el marcador {marker}, y solo la persona puede ponerlo. Reescribe el pasaje, o pide a la persona que ponga el marcador en su propio editor.",
|
|
3055
|
+
"prose.rule.requester-reference": "di el motivo técnico del cambio, sin decir quién lo pidió",
|
|
3056
|
+
"prose.rule.person-as-reason": "nombra la propiedad del cambio en lugar de la persona",
|
|
3057
|
+
"prose.rule.origin-project": "describe la propiedad técnica, sin citar de dónde salió",
|
|
3058
|
+
"prose.rule.first-person": "escribe lo que hace el cambio, en presente, sin yo ni tú",
|
|
3059
|
+
"prose.rule.padding": "quita el preámbulo y empieza por lo que cambió",
|
|
3060
|
+
"gate.require.prose": "el Markdown cambiado no parece escrito por una máquina",
|
|
3061
|
+
"gate.prose.unavailable": "no se pudieron leer los archivos Markdown cambiados (git no disponible)",
|
|
3062
|
+
"gate.prose.finding": "{file}:{line} parece escrito por una máquina ({rules})",
|
|
3063
|
+
"gate.prose.warning": "solo aviso en este preset: {file}:{line} parece escrito por una máquina ({rules})",
|
|
3064
|
+
"gate.prose.clean": "{count} archivo(s) Markdown cambiado(s), ninguno parece escrito por una máquina"
|
|
2983
3065
|
};
|
|
2984
3066
|
|
|
2985
3067
|
// src/i18n/pt-BR.json
|
|
@@ -3291,7 +3373,47 @@ var pt_BR_default = {
|
|
|
3291
3373
|
"gate.secrets.finding": "segredo {rule} em {file}:{line}",
|
|
3292
3374
|
"gate.secrets.incident": "incidente de segredo {id} está {state}",
|
|
3293
3375
|
"gate.secrets.clean": "nenhum segredo novo nas mudanças e nenhum incidente aberto",
|
|
3294
|
-
"gate.require.secrets": "nenhum segredo novo e nenhum incidente de segredo aberto"
|
|
3376
|
+
"gate.require.secrets": "nenhum segredo novo e nenhum incidente de segredo aberto",
|
|
3377
|
+
"prose.command.check": "Confere se o texto tem padrões que o leitor reconhece como escrita de IA (só local)",
|
|
3378
|
+
"prose.rule.opener-routine": "abra com o que aconteceu com alguém: um comando, um horário, uma mensagem de erro",
|
|
3379
|
+
"prose.rule.reassurance": "corte a frase de consolo; descreva o problema e deixe o leitor julgar",
|
|
3380
|
+
"prose.rule.slogan-closer": "termine num fato ou no próximo passo, sem slogan",
|
|
3381
|
+
"prose.rule.rhythmic-triplet": "junte as frases curtas numa só que diga o que cada parte faz",
|
|
3382
|
+
"prose.rule.imperative-fragment": "troque as ordens soltas por uma frase sobre o que o produto faz",
|
|
3383
|
+
"prose.rule.antithesis": "diga o que é e tire o contraste com o que não é",
|
|
3384
|
+
"prose.rule.abstract-noun": "troque o substantivo abstrato pela cena: quem fez o quê, onde",
|
|
3385
|
+
"prose.rule.crutch-word": "use a palavra simples, ou um número que prove o que diz",
|
|
3386
|
+
"prose.rule.colon-heading": "faça do título a pergunta ou a tarefa de quem lê",
|
|
3387
|
+
"prose.rule.decorative-emoji": "tire o emoji; o título já carrega o sentido",
|
|
3388
|
+
"prose.rule.em-dash-series": "use ponto, vírgula ou parênteses no lugar dos travessões",
|
|
3389
|
+
"prose.target.commit": "A mensagem de commit",
|
|
3390
|
+
"prose.target.pr": "O texto do PR",
|
|
3391
|
+
"prose.target.issue": "O texto da issue",
|
|
3392
|
+
"prose.span": "linhas {start}-{end}: {rules}",
|
|
3393
|
+
"prose.fix_item": "{rule}: {fix}",
|
|
3394
|
+
"prose.warn.command": "{target} tem cara de texto de IA ({spans}). Reescreva antes de enviar. {fixes}. Se for texto de marca escrito de propósito, mostre duas alternativas à pessoa.",
|
|
3395
|
+
"prose.block.command": "bloqueado: {target} tem cara de texto de IA ({spans}), e o preset deste projeto não deixa enviar. {fixes}. Reescreva e rode o comando de novo.",
|
|
3396
|
+
"prose.block.unreadable": "bloqueado: não deu para ler {file} para conferir o texto ({detail}). Confira o caminho e rode o comando de novo.",
|
|
3397
|
+
"prose.block.marker": "bloqueado: isto acrescentaria o marcador {marker} em {file}, e só a pessoa pode pôr esse marcador. Reescreva o trecho, ou peça à pessoa que ponha o marcador no editor dela.",
|
|
3398
|
+
"prose.check.file": "{file}: {count} trecho(s) com cara de texto de IA",
|
|
3399
|
+
"prose.check.span": " {file}:{start}-{end} pontos {score}: {rules}",
|
|
3400
|
+
"prose.check.fix": " faça no lugar: {fix}",
|
|
3401
|
+
"prose.check.allowed": "{file}: pulado, a pessoa marcou com {marker}",
|
|
3402
|
+
"prose.check.clean": "{count} arquivo(s) conferido(s), nada com cara de IA. Esse o macaco escreveu sozinho.",
|
|
3403
|
+
"prose.error.found": "{count} trecho(s) com cara de texto de IA",
|
|
3404
|
+
"prose.error.no_input": "nada para conferir: passe arquivos, --stdin, ou rode dentro de um repositório git para conferir os Markdown alterados",
|
|
3405
|
+
"prose.error.unreadable": "não deu para ler {file} ({detail})",
|
|
3406
|
+
"prose.block.marker_shell": "bloqueado: este comando escreve o marcador {marker}, e só a pessoa pode pôr esse marcador. Reescreva o trecho, ou peça à pessoa que ponha o marcador no editor dela.",
|
|
3407
|
+
"prose.rule.requester-reference": "diga o motivo técnico da mudança, sem dizer quem pediu",
|
|
3408
|
+
"prose.rule.person-as-reason": "nomeie a propriedade da mudança no lugar da pessoa",
|
|
3409
|
+
"prose.rule.origin-project": "descreva a propriedade técnica, sem citar de onde veio",
|
|
3410
|
+
"prose.rule.first-person": "escreva o que a mudança faz, no presente, sem eu nem você",
|
|
3411
|
+
"prose.rule.padding": "corte o preâmbulo e comece pelo que mudou",
|
|
3412
|
+
"gate.require.prose": "Markdown alterado sem cara de texto de máquina",
|
|
3413
|
+
"gate.prose.unavailable": "não deu para ler os arquivos Markdown alterados (git indisponível)",
|
|
3414
|
+
"gate.prose.finding": "{file}:{line} tem cara de texto de máquina ({rules})",
|
|
3415
|
+
"gate.prose.warning": "só aviso neste preset: {file}:{line} tem cara de texto de máquina ({rules})",
|
|
3416
|
+
"gate.prose.clean": "{count} arquivo(s) Markdown alterado(s), nenhum com cara de texto de máquina"
|
|
3295
3417
|
};
|
|
3296
3418
|
|
|
3297
3419
|
// src/i18n/catalogs.ts
|
|
@@ -3518,8 +3640,8 @@ var requirement2 = (want, ctx) => {
|
|
|
3518
3640
|
var gitCheck = (check2, ctx) => {
|
|
3519
3641
|
const wants = check2.require ?? ["committed"];
|
|
3520
3642
|
const missing = wants.flatMap((want) => {
|
|
3521
|
-
const
|
|
3522
|
-
return
|
|
3643
|
+
const problem4 = requirement2(want, ctx);
|
|
3644
|
+
return problem4 === void 0 ? [] : [problem4];
|
|
3523
3645
|
});
|
|
3524
3646
|
return missing.length > 0 ? failed(missing) : passed([note("gate.git.ok", { require: wants.join(", ") })]);
|
|
3525
3647
|
};
|
|
@@ -3678,6 +3800,668 @@ var prOutcome = (check2, ctx) => {
|
|
|
3678
3800
|
}
|
|
3679
3801
|
};
|
|
3680
3802
|
|
|
3803
|
+
// src/install/layout.ts
|
|
3804
|
+
var STATE_DIR = ".finitemonkey";
|
|
3805
|
+
var SETTINGS_FILE = ".claude/settings.json";
|
|
3806
|
+
var GITIGNORE_FILE = ".gitignore";
|
|
3807
|
+
var CONFIG_FILE = ".finitemonkey/config.yml";
|
|
3808
|
+
var LOCK_FILE = ".finitemonkey/lock.json";
|
|
3809
|
+
var BACKUPS_DIR = ".finitemonkey/backups";
|
|
3810
|
+
var LOCKS_DIR = ".finitemonkey/locks";
|
|
3811
|
+
var INSTALL_LOCK = ".finitemonkey/locks/install.lock";
|
|
3812
|
+
var LOCAL_BIN = "node_modules/finitemonkey/dist/finitemonkey.mjs";
|
|
3813
|
+
var VENDOR_BIN = ".finitemonkey/runtime/dist/finitemonkey.mjs";
|
|
3814
|
+
var VENDOR_PACKAGE = ".finitemonkey/runtime/package.json";
|
|
3815
|
+
var LOCK_SCHEMA = "finitemonkey/lock@1";
|
|
3816
|
+
var PLAN_SCHEMA = "finitemonkey/install-plan@1";
|
|
3817
|
+
var GENERATED_MARKER = "finitemonkey:generated v=1";
|
|
3818
|
+
var BLOCK_BEGIN = "# finitemonkey:begin v=1";
|
|
3819
|
+
var BLOCK_END = "# finitemonkey:end";
|
|
3820
|
+
var INSTALL_LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
3821
|
+
var PRESETS = ["neutral", "gabriel"];
|
|
3822
|
+
var parsePreset = (raw) => raw === void 0 ? "neutral" : PRESETS.find((preset) => preset === raw);
|
|
3823
|
+
var COMMITTED_STATE = ["config.yml", "runtime/", "tasks/", "rules.yml", "policy.yml", "findings/"];
|
|
3824
|
+
|
|
3825
|
+
// src/prose/preset.ts
|
|
3826
|
+
var PRESET_LINE = /^preset:\s*([A-Za-z0-9_-]+)\s*$/m;
|
|
3827
|
+
var presetFromConfig = (text6) => parsePreset(PRESET_LINE.exec(text6)?.[1]) ?? "neutral";
|
|
3828
|
+
var projectPreset = (runtime, root) => {
|
|
3829
|
+
const config = runtime.readText(toAbsolutePath(CONFIG_FILE, root));
|
|
3830
|
+
return config.ok ? presetFromConfig(config.value) : "neutral";
|
|
3831
|
+
};
|
|
3832
|
+
|
|
3833
|
+
// src/prose/catalog.ts
|
|
3834
|
+
var lazy = (build2) => {
|
|
3835
|
+
let value;
|
|
3836
|
+
return () => value ??= build2();
|
|
3837
|
+
};
|
|
3838
|
+
var bounded = (alternatives, flags) => new RegExp(`(?<![\\p{L}\\p{N}])(?:${alternatives.join("|")})(?![\\p{L}\\p{N}])`, flags);
|
|
3839
|
+
var phrases = (...alternatives) => bounded(alternatives, "iu");
|
|
3840
|
+
var casedPhrases = (...alternatives) => bounded(alternatives, "u");
|
|
3841
|
+
var OPENER = () => [
|
|
3842
|
+
phrases("you know the (?:routine|drill)", "we've all been there", "we all know the feeling", "sound familiar\\?"),
|
|
3843
|
+
phrases(
|
|
3844
|
+
"(?:você )?(?:já )?conhece (?:a rotina|o roteiro|o esquema)",
|
|
3845
|
+
"todo mundo já passou por isso",
|
|
3846
|
+
"soa familiar\\?"
|
|
3847
|
+
),
|
|
3848
|
+
phrases(
|
|
3849
|
+
"(?:ya )?conoces (?:la rutina|el guion|el procedimiento)",
|
|
3850
|
+
"todos hemos pasado por (?:eso|esto)",
|
|
3851
|
+
"¿te suena\\?"
|
|
3852
|
+
)
|
|
3853
|
+
];
|
|
3854
|
+
var REASSURANCE = () => [
|
|
3855
|
+
phrases(
|
|
3856
|
+
"(?:is|it's|that's|that is) not a character flaw",
|
|
3857
|
+
"nobody(?:'s| is)(?: here)? (?:lazy|to blame)",
|
|
3858
|
+
"no one(?:'s| is)(?: here)? (?:lazy|to blame)",
|
|
3859
|
+
"it(?:'s| is) not your fault",
|
|
3860
|
+
"you(?:'re| are) not alone"
|
|
3861
|
+
),
|
|
3862
|
+
phrases(
|
|
3863
|
+
"não é (?:um )?defeito de caráter",
|
|
3864
|
+
"ninguém (?:aqui )?é preguiçoso",
|
|
3865
|
+
"a culpa não é sua",
|
|
3866
|
+
"não é culpa sua",
|
|
3867
|
+
"você não está sozinh[oa]"
|
|
3868
|
+
),
|
|
3869
|
+
phrases("no es un defecto de carácter", "(?:aquí )?nadie es vago", "no es (?:tu|su) culpa", "no estás sol[oa]")
|
|
3870
|
+
];
|
|
3871
|
+
var SLOGAN = () => [
|
|
3872
|
+
phrases("and that changes everything", "that's the whole point", "simple as that", "it's that simple"),
|
|
3873
|
+
phrases("e isso muda tudo", "simples assim", "é simples assim", "e ponto final"),
|
|
3874
|
+
phrases("y eso lo cambia todo", "así de simple", "así de fácil", "y punto")
|
|
3875
|
+
];
|
|
3876
|
+
var ABSTRACT = () => [
|
|
3877
|
+
phrases(
|
|
3878
|
+
"the (?:pace|speed|rhythm|nature|reality) of (?:the )?(?:work|modern work|software|development)",
|
|
3879
|
+
"the reality is",
|
|
3880
|
+
"in today's (?:fast-paced|digital|modern) world"
|
|
3881
|
+
),
|
|
3882
|
+
phrases(
|
|
3883
|
+
"(?:o|a) (?:ritmo|velocidade|natureza|realidade) do (?:trabalho|desenvolvimento)",
|
|
3884
|
+
"a realidade é que",
|
|
3885
|
+
"no mundo (?:atual|de hoje)"
|
|
3886
|
+
),
|
|
3887
|
+
phrases(
|
|
3888
|
+
"(?:el|la) (?:ritmo|velocidad|naturaleza|realidad) del (?:trabajo|desarrollo)",
|
|
3889
|
+
"la realidad es que",
|
|
3890
|
+
"en el mundo (?:actual|de hoy)"
|
|
3891
|
+
)
|
|
3892
|
+
];
|
|
3893
|
+
var CRUTCH = () => [
|
|
3894
|
+
phrases(
|
|
3895
|
+
"delv(?:e|es|ed|ing)",
|
|
3896
|
+
"seamless(?:ly)?",
|
|
3897
|
+
"robust(?:ness)?",
|
|
3898
|
+
"leverag(?:e|es|ed|ing)",
|
|
3899
|
+
"game[- ]chang(?:er|ers|ing)",
|
|
3900
|
+
"it(?:'s| is) worth noting",
|
|
3901
|
+
"cutting[- ]edge",
|
|
3902
|
+
"unlock the (?:full )?(?:power|potential)",
|
|
3903
|
+
"supercharg(?:e|ed|es)",
|
|
3904
|
+
"effortless(?:ly)?",
|
|
3905
|
+
"in conclusion",
|
|
3906
|
+
"at the end of the day",
|
|
3907
|
+
"navigate the (?:complexities|landscape)"
|
|
3908
|
+
),
|
|
3909
|
+
phrases(
|
|
3910
|
+
"vale (?:ressaltar|destacar|lembrar que)",
|
|
3911
|
+
"cabe (?:destacar|ressaltar)",
|
|
3912
|
+
"é importante (?:ressaltar|destacar|notar)",
|
|
3913
|
+
"em (?:resumo|suma)",
|
|
3914
|
+
"robust(?:o|a|os|as|ez)",
|
|
3915
|
+
"alavanc(?:ar|a|ando|ado)",
|
|
3916
|
+
"divisor de águas",
|
|
3917
|
+
"sem esforço",
|
|
3918
|
+
"potencializ(?:ar|a|ando)",
|
|
3919
|
+
"de forma fluida"
|
|
3920
|
+
),
|
|
3921
|
+
phrases(
|
|
3922
|
+
"en (?:resumen|conclusión)",
|
|
3923
|
+
"cabe (?:destacar|señalar|mencionar)",
|
|
3924
|
+
"vale la pena (?:destacar|mencionar|señalar)",
|
|
3925
|
+
"es importante (?:destacar|señalar)",
|
|
3926
|
+
"robust(?:o|a|os|as|ez)",
|
|
3927
|
+
"apalanc(?:ar|a|ando)",
|
|
3928
|
+
"sin fisuras",
|
|
3929
|
+
"sin esfuerzo",
|
|
3930
|
+
"cambia las reglas del juego"
|
|
3931
|
+
)
|
|
3932
|
+
];
|
|
3933
|
+
var phraseRules = lazy(() => [
|
|
3934
|
+
{ rule: "opener-routine", weight: 3, patterns: OPENER() },
|
|
3935
|
+
{ rule: "reassurance", weight: 3, patterns: REASSURANCE() },
|
|
3936
|
+
{ rule: "slogan-closer", weight: 2, patterns: SLOGAN() },
|
|
3937
|
+
{ rule: "abstract-noun", weight: 2, patterns: ABSTRACT() },
|
|
3938
|
+
{ rule: "crutch-word", weight: 3, patterns: CRUTCH() }
|
|
3939
|
+
]);
|
|
3940
|
+
var IMPERATIVE_VERBS = /* @__PURE__ */ new Set([
|
|
3941
|
+
"type",
|
|
3942
|
+
"ship",
|
|
3943
|
+
"work",
|
|
3944
|
+
"write",
|
|
3945
|
+
"let",
|
|
3946
|
+
"build",
|
|
3947
|
+
"go",
|
|
3948
|
+
"stop",
|
|
3949
|
+
"start",
|
|
3950
|
+
"keep",
|
|
3951
|
+
"make",
|
|
3952
|
+
"focus",
|
|
3953
|
+
"forget",
|
|
3954
|
+
"trust",
|
|
3955
|
+
"code",
|
|
3956
|
+
"digite",
|
|
3957
|
+
"trabalhe",
|
|
3958
|
+
"produza",
|
|
3959
|
+
"deixe",
|
|
3960
|
+
"escreva",
|
|
3961
|
+
"faça",
|
|
3962
|
+
"pare",
|
|
3963
|
+
"comece",
|
|
3964
|
+
"entregue",
|
|
3965
|
+
"esqueça",
|
|
3966
|
+
"confie",
|
|
3967
|
+
"foque",
|
|
3968
|
+
"programe",
|
|
3969
|
+
"escribe",
|
|
3970
|
+
"teclea",
|
|
3971
|
+
"trabaja",
|
|
3972
|
+
"produce",
|
|
3973
|
+
"deja",
|
|
3974
|
+
"haz",
|
|
3975
|
+
"empieza",
|
|
3976
|
+
"olvida",
|
|
3977
|
+
"confía",
|
|
3978
|
+
"programa",
|
|
3979
|
+
"entrega"
|
|
3980
|
+
]);
|
|
3981
|
+
var STRUCTURE_WEIGHTS = {
|
|
3982
|
+
triplet: 1,
|
|
3983
|
+
imperative: 1,
|
|
3984
|
+
closer: 1,
|
|
3985
|
+
antithesis: 1,
|
|
3986
|
+
antithesisPair: 2,
|
|
3987
|
+
heading: 1,
|
|
3988
|
+
emoji: 1,
|
|
3989
|
+
dash: 1,
|
|
3990
|
+
dashSeries: 2
|
|
3991
|
+
};
|
|
3992
|
+
|
|
3993
|
+
// src/prose/marker.ts
|
|
3994
|
+
var ALLOW_MARKER = "finitemonkey:prose-allow";
|
|
3995
|
+
var ALLOW_FILE_MARKER = "finitemonkey:prose-allow-file";
|
|
3996
|
+
var ALLOW_COMMENT = `<!-- ${ALLOW_MARKER} -->`;
|
|
3997
|
+
var ALLOW_FILE_COMMENT = `<!-- ${ALLOW_FILE_MARKER} -->`;
|
|
3998
|
+
var LINE_MARKER = /<!--\s*finitemonkey:prose-allow\s*-->/;
|
|
3999
|
+
var FILE_MARKER = /<!--\s*finitemonkey:prose-allow-file\s*-->/;
|
|
4000
|
+
var ANY_MARKER = /<!--\s*finitemonkey:prose-allow(?:-file)?\s*-->/g;
|
|
4001
|
+
var INLINE_CODE = /`[^`]*`/g;
|
|
4002
|
+
var FENCE = /^\s*(```|~~~)/;
|
|
4003
|
+
var outsideCode = (line) => line.replace(INLINE_CODE, "");
|
|
4004
|
+
var unfencedLines = (text6) => {
|
|
4005
|
+
let fenced = false;
|
|
4006
|
+
return text6.split("\n").filter((line) => {
|
|
4007
|
+
if (FENCE.test(line)) fenced = !fenced;
|
|
4008
|
+
return !fenced && !FENCE.test(line);
|
|
4009
|
+
});
|
|
4010
|
+
};
|
|
4011
|
+
var allowsWholeFile = (text6) => unfencedLines(text6).some((line) => FILE_MARKER.test(outsideCode(line)));
|
|
4012
|
+
var allowsLine = (line) => LINE_MARKER.test(outsideCode(line));
|
|
4013
|
+
var markerCount = (text6) => text6.match(ANY_MARKER)?.length ?? 0;
|
|
4014
|
+
var isMarkerOnlyLine = (line) => allowsLine(line) && outsideCode(line).replace(LINE_MARKER, "").trim() === "";
|
|
4015
|
+
|
|
4016
|
+
// src/prose/extract.ts
|
|
4017
|
+
var FENCE2 = /^\s*(```|~~~)/;
|
|
4018
|
+
var HEADING = /^#{1,6}\s+/;
|
|
4019
|
+
var ITEM = /^\s*(?:[-*+]|\d+[.)])\s+/;
|
|
4020
|
+
var SKIPPED_LINE = /^\s*[|>]|^\s*<(?!!--)/;
|
|
4021
|
+
var INDENTED_CODE = /^(?: {4}|\t)/;
|
|
4022
|
+
var SENTENCE_END = /(?<=[.!?…])\s+/;
|
|
4023
|
+
var MASKS = [
|
|
4024
|
+
[/!\[[^\]]*\]\([^)]*\)/g, ""],
|
|
4025
|
+
[/\[[^\]]*\]\([^)]*\)/g, "x"],
|
|
4026
|
+
[/\[[^\]]+\]\[[^\]]*\]/g, "x"],
|
|
4027
|
+
[/`[^`]*`/g, "x"],
|
|
4028
|
+
[/https?:\/\/\S+/g, "x"],
|
|
4029
|
+
[/"[^"]*"|“[^”]*”|«[^»]*»/g, "x"],
|
|
4030
|
+
[/<[^>]+>/g, ""],
|
|
4031
|
+
[/\*\*|__/g, ""]
|
|
4032
|
+
];
|
|
4033
|
+
var maskInline = (line) => MASKS.reduce((text6, [pattern, replacement]) => text6.replace(pattern, replacement), line);
|
|
4034
|
+
var BlockReader = class {
|
|
4035
|
+
blocks = [];
|
|
4036
|
+
current;
|
|
4037
|
+
skipNext = false;
|
|
4038
|
+
flush() {
|
|
4039
|
+
const block3 = this.current;
|
|
4040
|
+
this.current = void 0;
|
|
4041
|
+
if (block3 === void 0 || block3.pieces.length === 0) return;
|
|
4042
|
+
if (this.skipNext) {
|
|
4043
|
+
this.skipNext = false;
|
|
4044
|
+
return;
|
|
4045
|
+
}
|
|
4046
|
+
this.blocks.push(block3);
|
|
4047
|
+
}
|
|
4048
|
+
skipNextBlock() {
|
|
4049
|
+
this.flush();
|
|
4050
|
+
this.skipNext = true;
|
|
4051
|
+
}
|
|
4052
|
+
start(kind, piece) {
|
|
4053
|
+
this.flush();
|
|
4054
|
+
this.current = { kind, pieces: [piece] };
|
|
4055
|
+
if (kind === "heading") this.flush();
|
|
4056
|
+
}
|
|
4057
|
+
append(piece) {
|
|
4058
|
+
this.current ??= { kind: "sentence", pieces: [] };
|
|
4059
|
+
this.current.pieces.push(piece);
|
|
4060
|
+
}
|
|
4061
|
+
get open() {
|
|
4062
|
+
return this.current !== void 0;
|
|
4063
|
+
}
|
|
4064
|
+
};
|
|
4065
|
+
var readLine = (reader, raw, line) => {
|
|
4066
|
+
if (raw.trim() === "") {
|
|
4067
|
+
reader.flush();
|
|
4068
|
+
} else if (isMarkerOnlyLine(raw)) {
|
|
4069
|
+
reader.skipNextBlock();
|
|
4070
|
+
} else if (allowsLine(raw)) {
|
|
4071
|
+
return;
|
|
4072
|
+
} else if (HEADING.test(raw)) {
|
|
4073
|
+
reader.start("heading", { text: raw.replace(HEADING, ""), line });
|
|
4074
|
+
} else if (SKIPPED_LINE.test(raw) || raw.trim().startsWith("<!--")) {
|
|
4075
|
+
reader.flush();
|
|
4076
|
+
} else if (ITEM.test(raw)) {
|
|
4077
|
+
reader.start("item", { text: raw.replace(ITEM, ""), line });
|
|
4078
|
+
} else if (INDENTED_CODE.test(raw) && !reader.open) {
|
|
4079
|
+
return;
|
|
4080
|
+
} else {
|
|
4081
|
+
reader.append({ text: raw.trim(), line });
|
|
4082
|
+
}
|
|
4083
|
+
};
|
|
4084
|
+
var skipFrontmatter = (lines2) => {
|
|
4085
|
+
if (lines2[0]?.trim() !== "---") return 0;
|
|
4086
|
+
const end = lines2.findIndex((line, index) => index > 0 && line.trim() === "---");
|
|
4087
|
+
return end < 0 ? 0 : end + 1;
|
|
4088
|
+
};
|
|
4089
|
+
var readBlocks = (lines2) => {
|
|
4090
|
+
const reader = new BlockReader();
|
|
4091
|
+
let fenced = false;
|
|
4092
|
+
let commented = false;
|
|
4093
|
+
for (let index = skipFrontmatter(lines2); index < lines2.length; index += 1) {
|
|
4094
|
+
const raw = lines2[index] ?? "";
|
|
4095
|
+
if (FENCE2.test(raw)) {
|
|
4096
|
+
fenced = !fenced;
|
|
4097
|
+
reader.flush();
|
|
4098
|
+
} else if (commented) {
|
|
4099
|
+
commented = !raw.includes("-->");
|
|
4100
|
+
} else if (!fenced) {
|
|
4101
|
+
if (raw.trim().startsWith("<!--") && !raw.includes("-->")) commented = true;
|
|
4102
|
+
readLine(reader, raw, index + 1);
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
reader.flush();
|
|
4106
|
+
return reader.blocks;
|
|
4107
|
+
};
|
|
4108
|
+
var splitBlock = (block3) => {
|
|
4109
|
+
if (block3.kind === "heading") return block3.pieces.map((piece) => ({ ...piece, text: maskInline(piece.text) }));
|
|
4110
|
+
const starts = [];
|
|
4111
|
+
let joined = "";
|
|
4112
|
+
for (const piece of block3.pieces) {
|
|
4113
|
+
starts.push(joined.length);
|
|
4114
|
+
joined += `${maskInline(piece.text)} `;
|
|
4115
|
+
}
|
|
4116
|
+
const lineAt = (offset2) => {
|
|
4117
|
+
const index = starts.findLastIndex((start) => start <= offset2);
|
|
4118
|
+
return block3.pieces[Math.max(index, 0)]?.line ?? 0;
|
|
4119
|
+
};
|
|
4120
|
+
let offset = 0;
|
|
4121
|
+
return joined.split(SENTENCE_END).flatMap((sentence) => {
|
|
4122
|
+
const at = joined.indexOf(sentence, offset);
|
|
4123
|
+
offset = at + sentence.length;
|
|
4124
|
+
const text6 = sentence.trim();
|
|
4125
|
+
return text6 === "" ? [] : [{ text: text6, line: lineAt(at) }];
|
|
4126
|
+
});
|
|
4127
|
+
};
|
|
4128
|
+
var unitsOf = (blocks) => {
|
|
4129
|
+
let section = 0;
|
|
4130
|
+
return blocks.flatMap((block3, paragraph) => {
|
|
4131
|
+
if (block3.kind === "heading") section += 1;
|
|
4132
|
+
const pieces = splitBlock(block3);
|
|
4133
|
+
const next = blocks[paragraph + 1];
|
|
4134
|
+
const closesSection = block3.kind === "sentence" && (next === void 0 || next.kind === "heading");
|
|
4135
|
+
return pieces.map((piece, index) => ({
|
|
4136
|
+
text: piece.text,
|
|
4137
|
+
line: piece.line,
|
|
4138
|
+
paragraph,
|
|
4139
|
+
section,
|
|
4140
|
+
kind: block3.kind,
|
|
4141
|
+
lastInParagraph: index === pieces.length - 1,
|
|
4142
|
+
closesSection
|
|
4143
|
+
}));
|
|
4144
|
+
});
|
|
4145
|
+
};
|
|
4146
|
+
var extractProse = (text6) => {
|
|
4147
|
+
if (allowsWholeFile(text6)) return { units: [], allowed: true };
|
|
4148
|
+
const lines2 = text6.split("\n").map(withoutCarriageReturn);
|
|
4149
|
+
return { units: unitsOf(readBlocks(lines2)), allowed: false };
|
|
4150
|
+
};
|
|
4151
|
+
|
|
4152
|
+
// src/prose/types.ts
|
|
4153
|
+
var DENSITY_WINDOW = 6;
|
|
4154
|
+
var DENSITY_THRESHOLD = 4;
|
|
4155
|
+
|
|
4156
|
+
// src/prose/record-catalog.ts
|
|
4157
|
+
var TECH_NOUNS = [
|
|
4158
|
+
"readme",
|
|
4159
|
+
"commit",
|
|
4160
|
+
"pr",
|
|
4161
|
+
"issue",
|
|
4162
|
+
"botão",
|
|
4163
|
+
"erro",
|
|
4164
|
+
"aviso",
|
|
4165
|
+
"bloqueio",
|
|
4166
|
+
"hook",
|
|
4167
|
+
"portão",
|
|
4168
|
+
"prompt",
|
|
4169
|
+
"modal",
|
|
4170
|
+
"rodapé",
|
|
4171
|
+
"cabeçalho",
|
|
4172
|
+
"título",
|
|
4173
|
+
"menu",
|
|
4174
|
+
"banner",
|
|
4175
|
+
"changelog",
|
|
4176
|
+
"site",
|
|
4177
|
+
"app",
|
|
4178
|
+
"formulário",
|
|
4179
|
+
"plugin",
|
|
4180
|
+
"comando",
|
|
4181
|
+
"contrato",
|
|
4182
|
+
"relatório",
|
|
4183
|
+
"projeto",
|
|
4184
|
+
"arquivo",
|
|
4185
|
+
"código",
|
|
4186
|
+
"pacote",
|
|
4187
|
+
"repositório",
|
|
4188
|
+
"produto",
|
|
4189
|
+
"módulo",
|
|
4190
|
+
"sistema"
|
|
4191
|
+
].join("|");
|
|
4192
|
+
var REQUESTER = () => [
|
|
4193
|
+
phrases(
|
|
4194
|
+
"as requested",
|
|
4195
|
+
"per (?:your|the user's|their) request",
|
|
4196
|
+
"as you (?:asked|requested|wanted)",
|
|
4197
|
+
"the user (?:asked|wants|wanted|requested)",
|
|
4198
|
+
"as discussed",
|
|
4199
|
+
"as agreed",
|
|
4200
|
+
"per (?:the|our) (?:conversation|discussion|chat)"
|
|
4201
|
+
),
|
|
4202
|
+
phrases(
|
|
4203
|
+
"conforme (?:solicitado|pedido|combinado)",
|
|
4204
|
+
"a pedido d[oa]",
|
|
4205
|
+
"como (?:pedido|solicitado|combinado|conversado)",
|
|
4206
|
+
"como você (?:pediu|quer|queria)",
|
|
4207
|
+
"o usuário (?:pediu|quer|queria)"
|
|
4208
|
+
),
|
|
4209
|
+
phrases(
|
|
4210
|
+
"según lo (?:pedido|solicitado|acordado)",
|
|
4211
|
+
"a (?:pedido|petición) de",
|
|
4212
|
+
"como (?:pediste|se pidió|acordamos)",
|
|
4213
|
+
"el usuario (?:pidió|quiere|quería)"
|
|
4214
|
+
)
|
|
4215
|
+
];
|
|
4216
|
+
var PERSON = () => [
|
|
4217
|
+
casedPhrases("\\p{Lu}\\p{Ll}+'s (?:text|request|idea|suggestion|wording|preference|feedback)"),
|
|
4218
|
+
phrases(`(?:texto|pedido|ideia|sugestão|preferência|gosto) d[oa] (?!(?:${TECH_NOUNS})(?![\\p{L}\\p{N}]))\\p{L}+`),
|
|
4219
|
+
casedPhrases("(?:texto|pedido|idea|sugerencia|preferencia) de \\p{Lu}\\p{Ll}+")
|
|
4220
|
+
];
|
|
4221
|
+
var ORIGIN = () => [
|
|
4222
|
+
phrases("in the style of", "(?:just )?like \\S+ does", "inspired by", "copied from"),
|
|
4223
|
+
phrases("no estilo d[oa]", "igual a[oà]s?", "inspirad[oa] n[oa]", "como o \\S+ faz"),
|
|
4224
|
+
phrases("al estilo de", "igual que", "como hace \\S+", "inspirad[oa] en")
|
|
4225
|
+
];
|
|
4226
|
+
var FIRST_PERSON = () => [
|
|
4227
|
+
phrases("I (?:changed|added|fixed|made|updated|removed|did|think|decided)", "you (?:asked|wanted|requested)"),
|
|
4228
|
+
phrases(
|
|
4229
|
+
"fiz",
|
|
4230
|
+
"eu (?:mudei|troquei|adicionei|corrigi|fiz|acho)",
|
|
4231
|
+
"mudei",
|
|
4232
|
+
"troquei",
|
|
4233
|
+
"adicionei",
|
|
4234
|
+
"corrigi",
|
|
4235
|
+
"você (?:pediu|quis)"
|
|
4236
|
+
),
|
|
4237
|
+
phrases("hice", "cambié", "añadí", "arreglé", "corregí", "pediste", "(?:tú|usted) (?:pediste|pidió)")
|
|
4238
|
+
];
|
|
4239
|
+
var PADDING = () => [
|
|
4240
|
+
phrases(
|
|
4241
|
+
"this commit",
|
|
4242
|
+
"this (?:PR|pull request) (?:aims|is meant|is intended|seeks|tries) to",
|
|
4243
|
+
"the (?:purpose|goal) of this (?:PR|pull request|commit)"
|
|
4244
|
+
),
|
|
4245
|
+
phrases("este commit", "este PR (?:tem como objetivo|visa|busca|pretende)", "o objetivo deste (?:PR|commit)"),
|
|
4246
|
+
phrases("este commit", "este PR (?:tiene como objetivo|busca|pretende)", "el objetivo de este (?:PR|commit)")
|
|
4247
|
+
];
|
|
4248
|
+
var recordRules = lazy(() => [
|
|
4249
|
+
{ rule: "requester-reference", weight: DENSITY_THRESHOLD, patterns: REQUESTER() },
|
|
4250
|
+
{ rule: "person-as-reason", weight: DENSITY_THRESHOLD, patterns: PERSON() },
|
|
4251
|
+
{ rule: "origin-project", weight: DENSITY_THRESHOLD, patterns: ORIGIN() },
|
|
4252
|
+
{ rule: "first-person", weight: DENSITY_THRESHOLD, patterns: FIRST_PERSON() },
|
|
4253
|
+
{ rule: "padding", weight: DENSITY_THRESHOLD, patterns: PADDING() }
|
|
4254
|
+
]);
|
|
4255
|
+
|
|
4256
|
+
// src/prose/structure.ts
|
|
4257
|
+
var SHORT_SENTENCE = 6;
|
|
4258
|
+
var SHORT_IMPERATIVE = 8;
|
|
4259
|
+
var SHORT_CLOSER = 8;
|
|
4260
|
+
var TRIAD_PART = 5;
|
|
4261
|
+
var WORD = /[\p{L}\p{N}]/u;
|
|
4262
|
+
var EMOJI = new RegExp("\\p{Extended_Pictographic}", "u");
|
|
4263
|
+
var COLON_HEADING = /^[^:]{1,40}:\s+\S/;
|
|
4264
|
+
var ANTITHESIS_TAIL = /,\s+(?:not|never|não|nunca|no)\s+([^,;:]+)$/iu;
|
|
4265
|
+
var NOT_BUT = /(?<![\p{L}])(?:not\s[^,.;]{1,40},\s*but|não\s[^,.;]{1,40},\s*mas|no\s[^,.;]{1,40},\s*sino)(?![\p{L}])/iu;
|
|
4266
|
+
var NEGATION = /(?<![\p{L}])(?:is not|isn't|it's not|that's not|that is not|não é|no es)(?![\p{L}])/iu;
|
|
4267
|
+
var AFFIRMATION = /^(?:it is|it's|this is|é|es|isso é|eso es)(?![\p{L}])/iu;
|
|
4268
|
+
var CONNECTIVES = new Set(
|
|
4269
|
+
"with in on at by for of to from if is are was never not x por em en com con de do da del se si é es são son nunca não no para que como".split(
|
|
4270
|
+
" "
|
|
4271
|
+
)
|
|
4272
|
+
);
|
|
4273
|
+
var wordsOf = (text6) => text6.split(/\s+/).filter((word) => WORD.test(word));
|
|
4274
|
+
var hit = (rule, units, index, weight) => {
|
|
4275
|
+
const unit = units[index];
|
|
4276
|
+
return { rule, unit: index, line: unit?.line ?? 0, weight, excerpt: unit?.text ?? "" };
|
|
4277
|
+
};
|
|
4278
|
+
var isProse = (unit) => unit?.kind === "sentence";
|
|
4279
|
+
var samePara = (units, from, count3) => {
|
|
4280
|
+
const first = units[from];
|
|
4281
|
+
if (!isProse(first)) return false;
|
|
4282
|
+
return units.slice(from, from + count3).every((unit) => isProse(unit) && unit.paragraph === first.paragraph);
|
|
4283
|
+
};
|
|
4284
|
+
var runsOf = (units, size, matches) => {
|
|
4285
|
+
const starts = [];
|
|
4286
|
+
for (let index = 0; index + size <= units.length; index += 1) {
|
|
4287
|
+
const window = units.slice(index, index + size);
|
|
4288
|
+
if (!samePara(units, index, size) || !window.every(matches)) continue;
|
|
4289
|
+
if (starts.at(-1) !== void 0 && index - (starts.at(-1) ?? 0) < size) continue;
|
|
4290
|
+
starts.push(index);
|
|
4291
|
+
}
|
|
4292
|
+
return starts;
|
|
4293
|
+
};
|
|
4294
|
+
var isShort = (limit) => (unit) => wordsOf(unit.text).length <= limit;
|
|
4295
|
+
var isImperative = (unit) => {
|
|
4296
|
+
const [first] = wordsOf(unit.text);
|
|
4297
|
+
return isShort(SHORT_IMPERATIVE)(unit) && IMPERATIVE_VERBS.has((first ?? "").toLowerCase());
|
|
4298
|
+
};
|
|
4299
|
+
var isPhrase = (part) => {
|
|
4300
|
+
const words = wordsOf(part);
|
|
4301
|
+
return words.length >= 2 && words.length <= TRIAD_PART && !CONNECTIVES.has((words[0] ?? "").toLowerCase());
|
|
4302
|
+
};
|
|
4303
|
+
var isCommaTriad = (unit) => {
|
|
4304
|
+
const parts = unit.text.replace(/[.!?…]+$/, "").split(/,\s*|\s+(?:and|e|y)\s+/);
|
|
4305
|
+
return parts.length === 3 && parts.every(isPhrase);
|
|
4306
|
+
};
|
|
4307
|
+
var triplets = (units) => [
|
|
4308
|
+
...runsOf(units, 3, isShort(SHORT_SENTENCE)).map(
|
|
4309
|
+
(index) => hit("rhythmic-triplet", units, index, STRUCTURE_WEIGHTS.triplet)
|
|
4310
|
+
),
|
|
4311
|
+
...units.flatMap(
|
|
4312
|
+
(unit, index) => isProse(unit) && unit.lastInParagraph && isCommaTriad(unit) ? [hit("rhythmic-triplet", units, index, STRUCTURE_WEIGHTS.triplet)] : []
|
|
4313
|
+
)
|
|
4314
|
+
];
|
|
4315
|
+
var imperatives = (units) => runsOf(units, 2, isImperative).map((index) => hit("imperative-fragment", units, index, STRUCTURE_WEIGHTS.imperative));
|
|
4316
|
+
var closers = (units) => units.flatMap((unit, index) => {
|
|
4317
|
+
const isCloser = isProse(unit) && unit.lastInParagraph && unit.closesSection && index > 0 && isShort(SHORT_CLOSER)(unit) && !/\d/.test(unit.text);
|
|
4318
|
+
return isCloser ? [hit("slogan-closer", units, index, STRUCTURE_WEIGHTS.closer)] : [];
|
|
4319
|
+
});
|
|
4320
|
+
var antitheses = (units) => units.flatMap((unit, index) => {
|
|
4321
|
+
if (unit.kind === "heading") return [];
|
|
4322
|
+
const tail2 = ANTITHESIS_TAIL.exec(unit.text)?.[1];
|
|
4323
|
+
if (tail2 !== void 0 && wordsOf(tail2).length <= SHORT_SENTENCE || NOT_BUT.test(unit.text)) {
|
|
4324
|
+
return [hit("antithesis", units, index, STRUCTURE_WEIGHTS.antithesis)];
|
|
4325
|
+
}
|
|
4326
|
+
const next = units[index + 1];
|
|
4327
|
+
const paired = NEGATION.test(unit.text) && next?.paragraph === unit.paragraph && AFFIRMATION.test(next.text);
|
|
4328
|
+
return paired ? [hit("antithesis", units, index, STRUCTURE_WEIGHTS.antithesisPair)] : [];
|
|
4329
|
+
});
|
|
4330
|
+
var headings = (units) => units.flatMap(
|
|
4331
|
+
(unit, index) => unit.kind === "heading" && COLON_HEADING.test(unit.text) ? [hit("colon-heading", units, index, STRUCTURE_WEIGHTS.heading)] : []
|
|
4332
|
+
);
|
|
4333
|
+
var emoji = (units) => units.flatMap(
|
|
4334
|
+
(unit, index) => EMOJI.test(unit.text) ? [hit("decorative-emoji", units, index, STRUCTURE_WEIGHTS.emoji)] : []
|
|
4335
|
+
);
|
|
4336
|
+
var dashes = (units) => {
|
|
4337
|
+
const perParagraph = /* @__PURE__ */ new Map();
|
|
4338
|
+
for (const unit of units) {
|
|
4339
|
+
const count3 = unit.text.split("—").length - 1;
|
|
4340
|
+
perParagraph.set(unit.paragraph, (perParagraph.get(unit.paragraph) ?? 0) + count3);
|
|
4341
|
+
}
|
|
4342
|
+
return [...perParagraph].flatMap(([paragraph, count3]) => {
|
|
4343
|
+
if (count3 === 0) return [];
|
|
4344
|
+
const index = units.findIndex((unit) => unit.paragraph === paragraph && unit.text.includes("—"));
|
|
4345
|
+
const weight = count3 >= 2 ? STRUCTURE_WEIGHTS.dashSeries : STRUCTURE_WEIGHTS.dash;
|
|
4346
|
+
return [hit("em-dash-series", units, index, weight)];
|
|
4347
|
+
});
|
|
4348
|
+
};
|
|
4349
|
+
var structureHits = (units) => [
|
|
4350
|
+
...triplets(units),
|
|
4351
|
+
...imperatives(units),
|
|
4352
|
+
...closers(units),
|
|
4353
|
+
...antitheses(units),
|
|
4354
|
+
...headings(units),
|
|
4355
|
+
...emoji(units),
|
|
4356
|
+
...dashes(units)
|
|
4357
|
+
];
|
|
4358
|
+
|
|
4359
|
+
// src/prose/score.ts
|
|
4360
|
+
var phraseHits = (units, catalog) => units.flatMap(
|
|
4361
|
+
(unit, index) => catalog.flatMap(({ rule, weight, patterns }) => {
|
|
4362
|
+
const match = patterns.map((pattern) => pattern.exec(unit.text)).find((found) => found !== null);
|
|
4363
|
+
return match === void 0 ? [] : [{ rule, unit: index, line: unit.line, weight, excerpt: match[0] }];
|
|
4364
|
+
})
|
|
4365
|
+
);
|
|
4366
|
+
var windowEnd = (units, from, size) => {
|
|
4367
|
+
const section = units[from]?.section;
|
|
4368
|
+
let to = from;
|
|
4369
|
+
while (to + 1 < Math.min(units.length, from + size) && units[to + 1]?.section === section) to += 1;
|
|
4370
|
+
return to;
|
|
4371
|
+
};
|
|
4372
|
+
var windowScore = (hits, window) => hits.filter((item) => item.unit >= window.from && item.unit <= window.to).reduce((sum, item) => sum + item.weight, 0);
|
|
4373
|
+
var hotWindows = (hits, units, size, threshold) => {
|
|
4374
|
+
const windows = [];
|
|
4375
|
+
for (let from = 0; from < units.length; from += 1) {
|
|
4376
|
+
const window = { from, to: windowEnd(units, from, size) };
|
|
4377
|
+
if (windowScore(hits, window) >= threshold) windows.push(window);
|
|
4378
|
+
}
|
|
4379
|
+
return windows;
|
|
4380
|
+
};
|
|
4381
|
+
var spanOf2 = (hits, from, to) => {
|
|
4382
|
+
const inside = hits.filter((item) => item.unit >= from && item.unit <= to);
|
|
4383
|
+
const lines2 = inside.map((item) => item.line);
|
|
4384
|
+
return {
|
|
4385
|
+
startLine: Math.min(...lines2),
|
|
4386
|
+
endLine: Math.max(...lines2),
|
|
4387
|
+
score: inside.reduce((sum, item) => sum + item.weight, 0),
|
|
4388
|
+
hits: inside
|
|
4389
|
+
};
|
|
4390
|
+
};
|
|
4391
|
+
var mergeSpans = (hits, windows) => {
|
|
4392
|
+
const ranges = [];
|
|
4393
|
+
for (const { from, to } of windows) {
|
|
4394
|
+
const previous = ranges.at(-1);
|
|
4395
|
+
if (previous !== void 0 && from <= previous[1] + 1) previous[1] = Math.max(previous[1], to);
|
|
4396
|
+
else ranges.push([from, to]);
|
|
4397
|
+
}
|
|
4398
|
+
return ranges.map(([from, to]) => spanOf2(hits, from, to)).filter((span) => span.hits.length > 0);
|
|
4399
|
+
};
|
|
4400
|
+
var byPosition = (left, right) => left.unit - right.unit || left.line - right.line;
|
|
4401
|
+
var scoreProse = (text6, options = {}) => {
|
|
4402
|
+
const { units, allowed: allowed2 } = extractProse(text6);
|
|
4403
|
+
const size = options.window ?? DENSITY_WINDOW;
|
|
4404
|
+
const threshold = options.threshold ?? DENSITY_THRESHOLD;
|
|
4405
|
+
const catalog = options.context === "record" ? [...phraseRules(), ...recordRules()] : phraseRules();
|
|
4406
|
+
const hits = [...phraseHits(units, catalog), ...structureHits(units)].sort(byPosition);
|
|
4407
|
+
const spans = mergeSpans(hits, hotWindows(hits, units, size, threshold));
|
|
4408
|
+
return { hits, spans, allowed: allowed2 };
|
|
4409
|
+
};
|
|
4410
|
+
var isFlagged = (report2) => report2.spans.length > 0;
|
|
4411
|
+
|
|
4412
|
+
// src/prose/gate.ts
|
|
4413
|
+
var MARKDOWN = /\.(?:md|markdown|mdx)$/i;
|
|
4414
|
+
var RECORD_FILE = /(?:^|\/)(?:CHANGELOG|RELEASE[-_]?NOTES|HISTORY)[^/]*$/i;
|
|
4415
|
+
var contextOf = (file) => RECORD_FILE.test(toPosixPath(file)) ? "record" : "docs";
|
|
4416
|
+
var listed = (runtime, root, args) => {
|
|
4417
|
+
const result = runtime.git(args, root);
|
|
4418
|
+
if (!result.ok) return reject(`git ${args.join(" ")} failed`);
|
|
4419
|
+
return accept(result.stdout.split("\0").map(toPosixPath));
|
|
4420
|
+
};
|
|
4421
|
+
var changedMarkdown = (runtime, root) => {
|
|
4422
|
+
const tracked = listed(runtime, root, ["diff", "--name-only", "-z", "--diff-filter=ACMR", "HEAD"]);
|
|
4423
|
+
if (!tracked.ok) return tracked;
|
|
4424
|
+
const untracked3 = listed(runtime, root, ["ls-files", "-z", "--others", "--exclude-standard"]);
|
|
4425
|
+
if (!untracked3.ok) return untracked3;
|
|
4426
|
+
const files = [...tracked.value, ...untracked3.value].filter(
|
|
4427
|
+
(file) => MARKDOWN.test(file) && !file.startsWith(".finitemonkey/")
|
|
4428
|
+
);
|
|
4429
|
+
return accept([...new Set(files)].sort());
|
|
4430
|
+
};
|
|
4431
|
+
var scoreFiles = (runtime, root, files, forced) => {
|
|
4432
|
+
const reports = [];
|
|
4433
|
+
for (const file of files) {
|
|
4434
|
+
const text6 = runtime.readText(toAbsolutePath(file, root));
|
|
4435
|
+
if (!text6.ok) return reject(`${file}: ${text6.error}`);
|
|
4436
|
+
reports.push({ file: toPosixPath(file), report: scoreProse(text6.value, { context: forced ?? contextOf(file) }) });
|
|
4437
|
+
}
|
|
4438
|
+
return accept(reports);
|
|
4439
|
+
};
|
|
4440
|
+
var proseGateStatus = (runtime, root) => {
|
|
4441
|
+
const changed = changedMarkdown(runtime, root);
|
|
4442
|
+
if (!changed.ok) return changed;
|
|
4443
|
+
const files = scoreFiles(runtime, root, changed.value);
|
|
4444
|
+
if (!files.ok) return files;
|
|
4445
|
+
return accept({
|
|
4446
|
+
files: files.value,
|
|
4447
|
+
flagged: files.value.filter((item) => isFlagged(item.report)),
|
|
4448
|
+
blocking: projectPreset(runtime, root) === "gabriel"
|
|
4449
|
+
});
|
|
4450
|
+
};
|
|
4451
|
+
|
|
4452
|
+
// src/gate/checks/prose.ts
|
|
4453
|
+
var MAX_LISTED3 = 10;
|
|
4454
|
+
var rulesOf = (span) => [...new Set(span.hits.map((hit2) => hit2.rule))].join(", ");
|
|
4455
|
+
var proseOutcome = (status) => {
|
|
4456
|
+
if (!status.ok) return unverified("tool_missing", [note("gate.prose.unavailable")]);
|
|
4457
|
+
const { files, flagged, blocking } = status.value;
|
|
4458
|
+
const key = blocking ? "gate.prose.finding" : "gate.prose.warning";
|
|
4459
|
+
const notes2 = flagged.flatMap((item) => item.report.spans.map((span) => ({ file: item.file, span }))).slice(0, MAX_LISTED3).map(({ file, span }) => note(key, { file, line: span.startLine, rules: rulesOf(span) }));
|
|
4460
|
+
if (notes2.length === 0) return passed([note("gate.prose.clean", { count: files.length })]);
|
|
4461
|
+
return blocking ? failed(notes2) : passed(notes2);
|
|
4462
|
+
};
|
|
4463
|
+
var proseCheck = (_check, ctx) => proseOutcome(proseGateStatus(ctx.runtime, ctx.paths.root));
|
|
4464
|
+
|
|
3681
4465
|
// src/secrets/baseline.ts
|
|
3682
4466
|
import { join as join7 } from "node:path";
|
|
3683
4467
|
var BASELINE_FILE2 = ".finitemonkey/findings/baseline.json";
|
|
@@ -3945,7 +4729,7 @@ var MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
|
3945
4729
|
var SKIPPED = /(?:^|\/)(?:\.git|node_modules|\.finitemonkey)\//;
|
|
3946
4730
|
var DIFF_FLAGS = ["-U0", "--no-color", "--no-ext-diff", "--no-renames"];
|
|
3947
4731
|
var binary = (text6) => text6.slice(0, 8e3).includes("\0");
|
|
3948
|
-
var
|
|
4732
|
+
var listed2 = (runtime, root, target) => {
|
|
3949
4733
|
const git = runtime.git(["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", target], root);
|
|
3950
4734
|
if (git.ok)
|
|
3951
4735
|
return accept(
|
|
@@ -3962,7 +4746,7 @@ var scanFile = (runtime, root, file) => {
|
|
|
3962
4746
|
return scanText(text6.value, { path: file }).map((finding) => locate(finding, file, finding.line));
|
|
3963
4747
|
};
|
|
3964
4748
|
var scanPath = (runtime, root, target) => {
|
|
3965
|
-
const files =
|
|
4749
|
+
const files = listed2(runtime, root, target);
|
|
3966
4750
|
if (!files.ok) return files;
|
|
3967
4751
|
return accept(files.value.filter((file) => !SKIPPED.test(file)).flatMap((file) => scanFile(runtime, root, file)));
|
|
3968
4752
|
};
|
|
@@ -3987,9 +4771,9 @@ var scanHistory = async (runtime, root) => {
|
|
|
3987
4771
|
|
|
3988
4772
|
// src/secrets/gate.ts
|
|
3989
4773
|
var untracked2 = (runtime, root) => {
|
|
3990
|
-
const
|
|
3991
|
-
if (!
|
|
3992
|
-
return
|
|
4774
|
+
const listed3 = runtime.git(["ls-files", "-z", "--others", "--exclude-standard"], root);
|
|
4775
|
+
if (!listed3.ok) return [];
|
|
4776
|
+
return listed3.stdout.split("\0").map(toPosixPath).filter((file) => file !== "" && !file.startsWith(".finitemonkey/")).flatMap((file) => scanFile(runtime, root, file));
|
|
3993
4777
|
};
|
|
3994
4778
|
var secretsGateStatus = (runtime, root, since) => {
|
|
3995
4779
|
const changed = scanStaged(runtime, root, true);
|
|
@@ -4004,7 +4788,7 @@ var secretsGateStatus = (runtime, root, since) => {
|
|
|
4004
4788
|
};
|
|
4005
4789
|
|
|
4006
4790
|
// src/gate/checks/secrets.ts
|
|
4007
|
-
var
|
|
4791
|
+
var MAX_LISTED4 = 10;
|
|
4008
4792
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
4009
4793
|
var sinceOf = (ctx) => {
|
|
4010
4794
|
const session = readSession(ctx.runtime, ctx.paths);
|
|
@@ -4015,8 +4799,8 @@ var secretsCheck = (_check, ctx) => {
|
|
|
4015
4799
|
if (!status.ok) return unverified("tool_missing", [note("gate.secrets.unavailable")]);
|
|
4016
4800
|
const { findings, openIncidents } = status.value;
|
|
4017
4801
|
const notes2 = [
|
|
4018
|
-
...findings.slice(0,
|
|
4019
|
-
...openIncidents.slice(0,
|
|
4802
|
+
...findings.slice(0, MAX_LISTED4).map((finding) => note("gate.secrets.finding", { file: finding.file, line: finding.line, rule: finding.ruleId })),
|
|
4803
|
+
...openIncidents.slice(0, MAX_LISTED4).map((incident) => note("gate.secrets.incident", { id: incident.id, state: incident.state }))
|
|
4020
4804
|
];
|
|
4021
4805
|
return notes2.length > 0 ? failed(notes2) : passed([note("gate.secrets.clean")]);
|
|
4022
4806
|
};
|
|
@@ -4169,7 +4953,7 @@ var forChange = (input, change) => {
|
|
|
4169
4953
|
var analyzeTree = (input) => input.changes.flatMap((change) => forChange(input, change));
|
|
4170
4954
|
|
|
4171
4955
|
// src/gate/checks/tests.ts
|
|
4172
|
-
var
|
|
4956
|
+
var MAX_LISTED5 = 10;
|
|
4173
4957
|
var violationNote = (violation) => note(`gate.tests.${violation.kind}`, {
|
|
4174
4958
|
file: violation.path,
|
|
4175
4959
|
before: violation.before ?? 0,
|
|
@@ -4194,7 +4978,7 @@ var treeViolations = (ctx) => {
|
|
|
4194
4978
|
var testsCheck = (check2, ctx) => {
|
|
4195
4979
|
const violations = treeViolations(ctx);
|
|
4196
4980
|
const grant = violations.length === 0 ? void 0 : activeGrant(ctx.runtime, ctx.paths, "tests", nowMs(ctx.runtime), ctx.sessionId);
|
|
4197
|
-
const notes2 = violations.slice(0,
|
|
4981
|
+
const notes2 = violations.slice(0, MAX_LISTED5).map(violationNote);
|
|
4198
4982
|
if (violations.length > 0 && grant === void 0) return failed([...notes2, note("gate.tests.fix")]);
|
|
4199
4983
|
const released2 = grant === void 0 ? {} : { releasedBy: `person@${grant.grantedAt}` };
|
|
4200
4984
|
const releaseNotes = grant === void 0 ? [] : [...notes2, note("gate.note.released", { guard: "tests", reason: grant.reason })];
|
|
@@ -4212,7 +4996,8 @@ var DEFAULT_CHECKS = {
|
|
|
4212
4996
|
git: gitCheck,
|
|
4213
4997
|
pr: prOutcome,
|
|
4214
4998
|
manual: manualCheck,
|
|
4215
|
-
secrets: secretsCheck
|
|
4999
|
+
secrets: secretsCheck,
|
|
5000
|
+
prose: proseCheck
|
|
4216
5001
|
};
|
|
4217
5002
|
|
|
4218
5003
|
// src/gate/report.ts
|
|
@@ -4483,10 +5268,10 @@ var helpFor = (ctx, spec) => {
|
|
|
4483
5268
|
var topicHelp = (ctx, registry, topic) => {
|
|
4484
5269
|
const resolved = resolveCommand(registry, topic);
|
|
4485
5270
|
if (resolved !== void 0) return helpFor(ctx, resolved.spec);
|
|
4486
|
-
const
|
|
5271
|
+
const problem4 = usageProblem("unknown_command", "error.unknown_command", "finitemonkey help", {
|
|
4487
5272
|
command: topic.join(" ")
|
|
4488
5273
|
});
|
|
4489
|
-
return failureFrom(ctx.t,
|
|
5274
|
+
return failureFrom(ctx.t, problem4);
|
|
4490
5275
|
};
|
|
4491
5276
|
var helpCommand = {
|
|
4492
5277
|
path: ["help"],
|
|
@@ -4821,7 +5606,7 @@ var testsGuard = (input) => {
|
|
|
4821
5606
|
|
|
4822
5607
|
// src/gate/hooks/config.ts
|
|
4823
5608
|
import { join as join12 } from "node:path";
|
|
4824
|
-
var
|
|
5609
|
+
var CONFIG_FILE2 = "config-hooks.json";
|
|
4825
5610
|
var GUARDED_SOURCES = /* @__PURE__ */ new Set(["user_settings", "project_settings", "local_settings"]);
|
|
4826
5611
|
var countIn = (value) => {
|
|
4827
5612
|
if (typeof value === "string") return /\bfinitemonkey\b|\bfm hook\b/.test(value) ? 1 : 0;
|
|
@@ -4844,11 +5629,11 @@ var read2 = (runtime, path) => {
|
|
|
4844
5629
|
return text6.ok ? text6.value : void 0;
|
|
4845
5630
|
};
|
|
4846
5631
|
var known = (runtime, paths) => {
|
|
4847
|
-
const raw = readJsonFile(runtime, paths.cache(
|
|
5632
|
+
const raw = readJsonFile(runtime, paths.cache(CONFIG_FILE2))?.["hooks"];
|
|
4848
5633
|
return isRecord(raw) ? Object.fromEntries(Object.entries(raw).filter((entry2) => typeof entry2[1] === "number")) : {};
|
|
4849
5634
|
};
|
|
4850
5635
|
var save = (runtime, paths, counts) => {
|
|
4851
|
-
writeJsonFile(runtime, paths.cache(
|
|
5636
|
+
writeJsonFile(runtime, paths.cache(CONFIG_FILE2), { hooks: { ...counts } });
|
|
4852
5637
|
};
|
|
4853
5638
|
var recordConfigHooks = (runtime, paths, env) => {
|
|
4854
5639
|
const counts = Object.fromEntries(
|
|
@@ -5242,9 +6027,10 @@ var subagentStopGate = (input) => isOwnWorktree(input) ? stopGate(input) : { kin
|
|
|
5242
6027
|
// src/gate/hooks/subagent.ts
|
|
5243
6028
|
import { join as join13 } from "node:path";
|
|
5244
6029
|
var NO_CONTRACT_AGENTS = /(^|:)(reviewer|verifier|explore|plan|claude-code-guide|statusline-setup)$/i;
|
|
6030
|
+
var KNOWN_KINDS = /* @__PURE__ */ new Set(["command", "tests", "diff", "placeholders", "git", "pr", "manual", "secrets", "prose"]);
|
|
5245
6031
|
var requirementText = (t, check2) => {
|
|
5246
6032
|
const key = `gate.require.${check2.kind}`;
|
|
5247
|
-
const known2 =
|
|
6033
|
+
const known2 = KNOWN_KINDS.has(check2.kind);
|
|
5248
6034
|
return `${check2.id} ${t(known2 ? key : "gate.require.other", { command: check2.run ?? check2.kind, kind: check2.kind })}`;
|
|
5249
6035
|
};
|
|
5250
6036
|
var contractFacts = (t, contract, briefing) => [
|
|
@@ -5260,8 +6046,8 @@ var subagentStart = (input) => {
|
|
|
5260
6046
|
const { runtime, t } = input;
|
|
5261
6047
|
const active = readState(runtime, paths).active;
|
|
5262
6048
|
if (active === void 0) return { kind: "allow" };
|
|
5263
|
-
const disk = (
|
|
5264
|
-
const text6 = runtime.readText(toAbsolutePath(join13(paths.root,
|
|
6049
|
+
const disk = (relative8) => {
|
|
6050
|
+
const text6 = runtime.readText(toAbsolutePath(join13(paths.root, relative8)));
|
|
5265
6051
|
return text6.ok ? text6.value : void 0;
|
|
5266
6052
|
};
|
|
5267
6053
|
const loaded = loadTask(runtime, paths, { disk, base: () => void 0 }, active);
|
|
@@ -5282,14 +6068,8 @@ var PORTAO_HANDLERS = {
|
|
|
5282
6068
|
ConfigChange: [configChange]
|
|
5283
6069
|
};
|
|
5284
6070
|
|
|
5285
|
-
// src/
|
|
5286
|
-
|
|
5287
|
-
var describeFindings = (t, items) => {
|
|
5288
|
-
const listed2 = items.slice(0, MAX_LISTED5).map(({ ruleId, where }) => t("secrets.found_item", { rule: ruleId, where }));
|
|
5289
|
-
const rest = items.length - listed2.length;
|
|
5290
|
-
return rest > 0 ? `${listed2.join("; ")}; ${t("secrets.found_more", { count: rest })}` : listed2.join("; ");
|
|
5291
|
-
};
|
|
5292
|
-
var lineWhere = (t, finding) => t("secrets.where.line", { line: finding.line, column: finding.column });
|
|
6071
|
+
// src/prose/marker-guard.ts
|
|
6072
|
+
import { relative as relative5 } from "node:path";
|
|
5293
6073
|
|
|
5294
6074
|
// src/secrets/guards/tool-input.ts
|
|
5295
6075
|
var toolName2 = (payload) => optionalString(payload.raw, "tool_name") ?? "";
|
|
@@ -5332,6 +6112,305 @@ var stringLeaves = (value, out = []) => {
|
|
|
5332
6112
|
return out;
|
|
5333
6113
|
};
|
|
5334
6114
|
|
|
6115
|
+
// src/prose/shell-words.ts
|
|
6116
|
+
var HEREDOC = /<<-?[ \t]*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1[^\n]*\n([\s\S]*?)\n[ \t]*\2[ \t]*(?=\n|$)/g;
|
|
6117
|
+
var HEREDOC_REF = /<<__FM_HEREDOC_(\d+)__/;
|
|
6118
|
+
var SEPARATOR = /[;&|\n]/;
|
|
6119
|
+
var ASSIGNMENT2 = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
6120
|
+
var WRAPPERS2 = /* @__PURE__ */ new Set(["sudo", "command", "exec", "time", "nice", "nohup", "builtin", "env"]);
|
|
6121
|
+
var liftHeredocs = (command) => {
|
|
6122
|
+
const heredocs = [];
|
|
6123
|
+
const text6 = command.replace(HEREDOC, (_match, _quote, _tag, body) => {
|
|
6124
|
+
heredocs.push(body);
|
|
6125
|
+
return `<<__FM_HEREDOC_${String(heredocs.length - 1)}__`;
|
|
6126
|
+
});
|
|
6127
|
+
return { text: text6, heredocs };
|
|
6128
|
+
};
|
|
6129
|
+
var endWord = (scan) => {
|
|
6130
|
+
if (scan.word !== void 0) scan.words.push(scan.word);
|
|
6131
|
+
scan.word = void 0;
|
|
6132
|
+
};
|
|
6133
|
+
var endSegment = (scan) => {
|
|
6134
|
+
endWord(scan);
|
|
6135
|
+
if (scan.words.length > 0) scan.segments.push(scan.words);
|
|
6136
|
+
scan.words = [];
|
|
6137
|
+
};
|
|
6138
|
+
var quotedEnd2 = (text6, from, quote) => {
|
|
6139
|
+
let index = from;
|
|
6140
|
+
while (index < text6.length && text6[index] !== quote) index += quote === '"' && text6[index] === "\\" ? 2 : 1;
|
|
6141
|
+
return index;
|
|
6142
|
+
};
|
|
6143
|
+
var unescape = (value) => value.replace(/\\(["\\$`])/g, "$1");
|
|
6144
|
+
var splitWords = (text6) => {
|
|
6145
|
+
const scan = { words: [], segments: [], word: void 0 };
|
|
6146
|
+
for (let index = 0; index < text6.length; index += 1) {
|
|
6147
|
+
const char = text6[index] ?? "";
|
|
6148
|
+
if (char === '"' || char === "'") {
|
|
6149
|
+
const end = quotedEnd2(text6, index + 1, char);
|
|
6150
|
+
const inner = text6.slice(index + 1, end);
|
|
6151
|
+
scan.word = (scan.word ?? "") + (char === '"' ? unescape(inner) : inner);
|
|
6152
|
+
index = end;
|
|
6153
|
+
} else if (char === "\\" && index + 1 < text6.length) {
|
|
6154
|
+
scan.word = (scan.word ?? "") + (text6[index + 1] === "\n" ? "" : text6[index + 1] ?? "");
|
|
6155
|
+
index += 1;
|
|
6156
|
+
} else if (SEPARATOR.test(char)) {
|
|
6157
|
+
endSegment(scan);
|
|
6158
|
+
} else if (/\s/.test(char)) {
|
|
6159
|
+
endWord(scan);
|
|
6160
|
+
} else {
|
|
6161
|
+
scan.word = (scan.word ?? "") + char;
|
|
6162
|
+
}
|
|
6163
|
+
}
|
|
6164
|
+
endSegment(scan);
|
|
6165
|
+
return scan.segments;
|
|
6166
|
+
};
|
|
6167
|
+
var segmentOf = (words) => {
|
|
6168
|
+
let index = 0;
|
|
6169
|
+
while (index < words.length && (ASSIGNMENT2.test(words[index] ?? "") || WRAPPERS2.has(words[index] ?? ""))) index += 1;
|
|
6170
|
+
const program = words[index];
|
|
6171
|
+
if (program === void 0) return void 0;
|
|
6172
|
+
return { program: program.split(/[\\/]/).pop() ?? program, args: words.slice(index + 1) };
|
|
6173
|
+
};
|
|
6174
|
+
var shellWords = (command) => {
|
|
6175
|
+
const { text: text6, heredocs } = liftHeredocs(command);
|
|
6176
|
+
const segments = splitWords(text6).flatMap((words) => {
|
|
6177
|
+
const segment = segmentOf(words);
|
|
6178
|
+
return segment === void 0 ? [] : [segment];
|
|
6179
|
+
});
|
|
6180
|
+
return { segments, heredocs };
|
|
6181
|
+
};
|
|
6182
|
+
var heredocIn = (value, heredocs) => {
|
|
6183
|
+
const index = HEREDOC_REF.exec(value)?.[1];
|
|
6184
|
+
return index === void 0 ? void 0 : heredocs[Number(index)];
|
|
6185
|
+
};
|
|
6186
|
+
|
|
6187
|
+
// src/prose/marker-guard.ts
|
|
6188
|
+
var ALLOW = { kind: "allow" };
|
|
6189
|
+
var SHELLS2 = /* @__PURE__ */ new Set(["Bash", "PowerShell"]);
|
|
6190
|
+
var READ_ONLY = /* @__PURE__ */ new Set(["grep", "rg", "cat", "less", "head", "tail", "ls", "wc", "finitemonkey", "fm"]);
|
|
6191
|
+
var GIT_READ_ONLY = /* @__PURE__ */ new Set(["grep", "log", "show", "diff", "status", "blame"]);
|
|
6192
|
+
var counted2 = (input, key) => markerCount(optionalString(input, key) ?? "");
|
|
6193
|
+
var existingCount = (input, file) => {
|
|
6194
|
+
if (file === "") return 0;
|
|
6195
|
+
const read3 = input.runtime.readText(toAbsolutePath(file, input.cwd));
|
|
6196
|
+
return read3.ok ? markerCount(read3.value) : 0;
|
|
6197
|
+
};
|
|
6198
|
+
var editPairs = (input) => {
|
|
6199
|
+
const edits = input["edits"];
|
|
6200
|
+
if (!Array.isArray(edits)) return [];
|
|
6201
|
+
return edits.flatMap((edit) => isRecord(edit) ? [[counted2(edit, "old_string"), counted2(edit, "new_string")]] : []);
|
|
6202
|
+
};
|
|
6203
|
+
var addsMarker = (input, tool, inputs, file) => {
|
|
6204
|
+
switch (tool) {
|
|
6205
|
+
case "Write":
|
|
6206
|
+
return counted2(inputs, "content") > existingCount(input, file);
|
|
6207
|
+
case "Edit":
|
|
6208
|
+
return counted2(inputs, "new_string") > counted2(inputs, "old_string");
|
|
6209
|
+
case "MultiEdit":
|
|
6210
|
+
return editPairs(inputs).reduce((sum, [before, after]) => sum + after - before, 0) > 0;
|
|
6211
|
+
case "NotebookEdit":
|
|
6212
|
+
return counted2(inputs, "new_source") > 0;
|
|
6213
|
+
default:
|
|
6214
|
+
return false;
|
|
6215
|
+
}
|
|
6216
|
+
};
|
|
6217
|
+
var isReadOnly = (program, args) => READ_ONLY.has(program) || program === "git" && GIT_READ_ONLY.has(args[0] ?? "");
|
|
6218
|
+
var shellAddsMarker = (command) => markerCount(command) > 0 && shellWords(command).segments.some((segment) => !isReadOnly(segment.program, segment.args));
|
|
6219
|
+
var shown = (cwd, file) => {
|
|
6220
|
+
const rel = toPosixPath(relative5(cwd, file));
|
|
6221
|
+
return file === "" || rel.startsWith("..") ? toPosixPath(file) : rel;
|
|
6222
|
+
};
|
|
6223
|
+
var blocked = (input, file) => ({
|
|
6224
|
+
kind: "block",
|
|
6225
|
+
reason: input.t("prose.block.marker", { marker: ALLOW_COMMENT, file })
|
|
6226
|
+
});
|
|
6227
|
+
var proseMarkerGuard = (input) => {
|
|
6228
|
+
const tool = toolName2(input.payload);
|
|
6229
|
+
const inputs = toolInput2(input.payload);
|
|
6230
|
+
if (SHELLS2.has(tool)) {
|
|
6231
|
+
if (!shellAddsMarker(optionalString(inputs, "command") ?? "")) return ALLOW;
|
|
6232
|
+
return { kind: "block", reason: input.t("prose.block.marker_shell", { marker: ALLOW_COMMENT }) };
|
|
6233
|
+
}
|
|
6234
|
+
const file = optionalString(inputs, "file_path") ?? optionalString(inputs, "notebook_path") ?? "";
|
|
6235
|
+
return addsMarker(input, tool, inputs, file) ? blocked(input, shown(input.cwd, file)) : ALLOW;
|
|
6236
|
+
};
|
|
6237
|
+
|
|
6238
|
+
// src/secrets/project.ts
|
|
6239
|
+
var projectRoot = (ctx) => {
|
|
6240
|
+
const top = ctx.runtime.git(["rev-parse", "--show-toplevel"], ctx.cwd);
|
|
6241
|
+
const path = top.stdout.trim();
|
|
6242
|
+
return top.ok && path !== "" ? toAbsolutePath(path) : ctx.cwd;
|
|
6243
|
+
};
|
|
6244
|
+
var isPerson = (ctx) => ctx.io.stdin.isTTY === true && ctx.env["CLAUDECODE"] === void 0 && ctx.env["CLAUDE_CODE_ENTRYPOINT"] === void 0;
|
|
6245
|
+
var personName = (ctx) => {
|
|
6246
|
+
const name = ctx.runtime.git(["config", "user.name"], ctx.cwd).stdout.trim();
|
|
6247
|
+
return name !== "" ? name : ctx.env["USER"] ?? ctx.env["USERNAME"] ?? "person";
|
|
6248
|
+
};
|
|
6249
|
+
|
|
6250
|
+
// src/prose/command-text.ts
|
|
6251
|
+
var GIT_FLAGS = { title: [], body: ["-m", "--message"], file: ["-F", "--file"] };
|
|
6252
|
+
var GH_FLAGS = { title: ["-t", "--title"], body: ["-b", "--body"], file: ["-F", "--body-file"] };
|
|
6253
|
+
var GIT_GLOBAL_WITH_VALUE = /* @__PURE__ */ new Set(["-C", "-c", "--git-dir", "--work-tree", "--namespace"]);
|
|
6254
|
+
var CLUSTER_WITH_MESSAGE = /^-[a-zA-Z]*m$/;
|
|
6255
|
+
var flagValues = (args, spec) => {
|
|
6256
|
+
const known2 = [...spec.title, ...spec.body, ...spec.file];
|
|
6257
|
+
const out = [];
|
|
6258
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
6259
|
+
const arg = args[index] ?? "";
|
|
6260
|
+
const equals = arg.indexOf("=");
|
|
6261
|
+
const name = arg.startsWith("--") && equals > 0 ? arg.slice(0, equals) : arg;
|
|
6262
|
+
if (name !== arg && known2.includes(name)) {
|
|
6263
|
+
out.push({ flag: name, value: arg.slice(equals + 1) });
|
|
6264
|
+
} else if (known2.includes(arg) || spec === GIT_FLAGS && CLUSTER_WITH_MESSAGE.test(arg)) {
|
|
6265
|
+
out.push({ flag: known2.includes(arg) ? arg : "-m", value: args[index + 1] ?? "" });
|
|
6266
|
+
index += 1;
|
|
6267
|
+
}
|
|
6268
|
+
}
|
|
6269
|
+
return out;
|
|
6270
|
+
};
|
|
6271
|
+
var sourceOf = (value, isFile, segment, heredocs) => {
|
|
6272
|
+
const inline3 = heredocIn(value, heredocs);
|
|
6273
|
+
if (inline3 !== void 0) return { kind: "inline", text: inline3 };
|
|
6274
|
+
if (!isFile) return { kind: "inline", text: value };
|
|
6275
|
+
if (value !== "-") return { kind: "file", path: value };
|
|
6276
|
+
const piped = segment.args.map((arg) => heredocIn(arg, heredocs)).find((text6) => text6 !== void 0);
|
|
6277
|
+
return { kind: "inline", text: piped ?? "" };
|
|
6278
|
+
};
|
|
6279
|
+
var collect2 = (target, segment, args, spec, heredocs) => {
|
|
6280
|
+
const values = flagValues(args, spec);
|
|
6281
|
+
if (values.length === 0) return void 0;
|
|
6282
|
+
const title = values.find((item) => spec.title.includes(item.flag));
|
|
6283
|
+
const bodies = values.filter((item) => !spec.title.includes(item.flag)).map((item) => sourceOf(item.value, spec.file.includes(item.flag), segment, heredocs));
|
|
6284
|
+
return { target, title: title === void 0 ? void 0 : sourceOf(title.value, false, segment, heredocs), bodies };
|
|
6285
|
+
};
|
|
6286
|
+
var gitSubcommand = (args) => {
|
|
6287
|
+
let index = 0;
|
|
6288
|
+
while (index < args.length && (args[index] ?? "").startsWith("-")) {
|
|
6289
|
+
index += GIT_GLOBAL_WITH_VALUE.has(args[index] ?? "") ? 2 : 1;
|
|
6290
|
+
}
|
|
6291
|
+
return index;
|
|
6292
|
+
};
|
|
6293
|
+
var fromSegment = (segment, heredocs) => {
|
|
6294
|
+
if (segment.program === "git") {
|
|
6295
|
+
const at = gitSubcommand(segment.args);
|
|
6296
|
+
if (segment.args[at] !== "commit") return void 0;
|
|
6297
|
+
return collect2("commit", segment, segment.args.slice(at + 1), GIT_FLAGS, heredocs);
|
|
6298
|
+
}
|
|
6299
|
+
if (segment.program !== "gh") return void 0;
|
|
6300
|
+
const [noun, verb] = segment.args;
|
|
6301
|
+
const isPr = noun === "pr" && (verb === "create" || verb === "edit");
|
|
6302
|
+
const isIssue = noun === "issue" && (verb === "create" || verb === "edit");
|
|
6303
|
+
if (!isPr && !isIssue) return void 0;
|
|
6304
|
+
return collect2(isPr ? "pr" : "issue", segment, segment.args.slice(2), GH_FLAGS, heredocs);
|
|
6305
|
+
};
|
|
6306
|
+
var proseInCommand = (command) => {
|
|
6307
|
+
const { segments, heredocs } = shellWords(command);
|
|
6308
|
+
return segments.flatMap((segment) => {
|
|
6309
|
+
const found = fromSegment(segment, heredocs);
|
|
6310
|
+
return found === void 0 ? [] : [found];
|
|
6311
|
+
});
|
|
6312
|
+
};
|
|
6313
|
+
|
|
6314
|
+
// src/prose/describe.ts
|
|
6315
|
+
var RULE_FIX_KEYS = {
|
|
6316
|
+
"opener-routine": "prose.rule.opener-routine",
|
|
6317
|
+
reassurance: "prose.rule.reassurance",
|
|
6318
|
+
"slogan-closer": "prose.rule.slogan-closer",
|
|
6319
|
+
"rhythmic-triplet": "prose.rule.rhythmic-triplet",
|
|
6320
|
+
"imperative-fragment": "prose.rule.imperative-fragment",
|
|
6321
|
+
antithesis: "prose.rule.antithesis",
|
|
6322
|
+
"abstract-noun": "prose.rule.abstract-noun",
|
|
6323
|
+
"crutch-word": "prose.rule.crutch-word",
|
|
6324
|
+
"colon-heading": "prose.rule.colon-heading",
|
|
6325
|
+
"decorative-emoji": "prose.rule.decorative-emoji",
|
|
6326
|
+
"em-dash-series": "prose.rule.em-dash-series",
|
|
6327
|
+
"requester-reference": "prose.rule.requester-reference",
|
|
6328
|
+
"person-as-reason": "prose.rule.person-as-reason",
|
|
6329
|
+
"origin-project": "prose.rule.origin-project",
|
|
6330
|
+
"first-person": "prose.rule.first-person",
|
|
6331
|
+
padding: "prose.rule.padding"
|
|
6332
|
+
};
|
|
6333
|
+
var MAX_SPANS = 3;
|
|
6334
|
+
var MAX_FIXES = 3;
|
|
6335
|
+
var rulesOf2 = (spans) => {
|
|
6336
|
+
const weights = /* @__PURE__ */ new Map();
|
|
6337
|
+
for (const hit2 of spans.flatMap((span) => span.hits))
|
|
6338
|
+
weights.set(hit2.rule, (weights.get(hit2.rule) ?? 0) + hit2.weight);
|
|
6339
|
+
return [...weights].sort((left, right) => right[1] - left[1]).map(([rule]) => rule);
|
|
6340
|
+
};
|
|
6341
|
+
var spanRules = (span) => {
|
|
6342
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6343
|
+
return span.hits.filter((hit2) => !seen.has(hit2.rule) && seen.add(hit2.rule)).map(
|
|
6344
|
+
(hit2) => hit2.rule === "crutch-word" || hit2.rule === "opener-routine" ? `${hit2.rule} "${hit2.excerpt}"` : hit2.rule
|
|
6345
|
+
).join(", ");
|
|
6346
|
+
};
|
|
6347
|
+
var describeSpans = (t, spans) => spans.slice(0, MAX_SPANS).map((span) => t("prose.span", { start: span.startLine, end: span.endLine, rules: spanRules(span) })).join("; ");
|
|
6348
|
+
var describeFixes = (t, spans) => rulesOf2(spans).slice(0, MAX_FIXES).map((rule) => t("prose.fix_item", { rule, fix: t(RULE_FIX_KEYS[rule]) })).join("; ");
|
|
6349
|
+
|
|
6350
|
+
// src/prose/text-guard.ts
|
|
6351
|
+
var ALLOW2 = { kind: "allow" };
|
|
6352
|
+
var SHELLS3 = /* @__PURE__ */ new Set(["Bash", "PowerShell"]);
|
|
6353
|
+
var TARGET_KEYS = {
|
|
6354
|
+
commit: "prose.target.commit",
|
|
6355
|
+
pr: "prose.target.pr",
|
|
6356
|
+
issue: "prose.target.issue"
|
|
6357
|
+
};
|
|
6358
|
+
var readSource = (input, source) => {
|
|
6359
|
+
if (source.kind === "inline") return accept(source.text);
|
|
6360
|
+
const read3 = input.runtime.readText(toAbsolutePath(source.path, input.cwd));
|
|
6361
|
+
return read3.ok ? read3 : reject({ file: source.path, detail: read3.error });
|
|
6362
|
+
};
|
|
6363
|
+
var textOf = (input, found) => {
|
|
6364
|
+
const parts = [];
|
|
6365
|
+
for (const source of [...found.title === void 0 ? [] : [found.title], ...found.bodies]) {
|
|
6366
|
+
const read3 = readSource(input, source);
|
|
6367
|
+
if (!read3.ok) return read3;
|
|
6368
|
+
parts.push(read3.value);
|
|
6369
|
+
}
|
|
6370
|
+
return accept(parts.join("\n\n"));
|
|
6371
|
+
};
|
|
6372
|
+
var decisionFor = (input, found, blocking) => {
|
|
6373
|
+
const text6 = textOf(input, found);
|
|
6374
|
+
if (!text6.ok) {
|
|
6375
|
+
return blocking ? { kind: "block", reason: input.t("prose.block.unreadable", { ...text6.error }) } : void 0;
|
|
6376
|
+
}
|
|
6377
|
+
const report2 = scoreProse(text6.value, { context: "record" });
|
|
6378
|
+
if (report2.allowed || report2.spans.length === 0) return void 0;
|
|
6379
|
+
const params2 = {
|
|
6380
|
+
target: input.t(TARGET_KEYS[found.target]),
|
|
6381
|
+
spans: describeSpans(input.t, report2.spans),
|
|
6382
|
+
fixes: describeFixes(input.t, report2.spans)
|
|
6383
|
+
};
|
|
6384
|
+
if (blocking) return { kind: "block", reason: input.t("prose.block.command", params2) };
|
|
6385
|
+
const additionalContext = `finitemonkey: ${input.t("prose.warn.command", params2)}`;
|
|
6386
|
+
return { kind: "allow", output: { hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext } } };
|
|
6387
|
+
};
|
|
6388
|
+
var proseCommandGuard = (input) => {
|
|
6389
|
+
if (!SHELLS3.has(toolName2(input.payload))) return ALLOW2;
|
|
6390
|
+
const found = proseInCommand(optionalString(toolInput2(input.payload), "command") ?? "");
|
|
6391
|
+
if (found.length === 0) return ALLOW2;
|
|
6392
|
+
const blocking = projectPreset(input.runtime, projectRoot(input)) === "gabriel";
|
|
6393
|
+
for (const item of found) {
|
|
6394
|
+
const decision = decisionFor(input, item, blocking);
|
|
6395
|
+
if (decision !== void 0) return decision;
|
|
6396
|
+
}
|
|
6397
|
+
return ALLOW2;
|
|
6398
|
+
};
|
|
6399
|
+
|
|
6400
|
+
// src/prose/hooks.ts
|
|
6401
|
+
var PROSE_HANDLERS = {
|
|
6402
|
+
PreToolUse: [proseMarkerGuard, proseCommandGuard]
|
|
6403
|
+
};
|
|
6404
|
+
|
|
6405
|
+
// src/secrets/guards/describe.ts
|
|
6406
|
+
var MAX_LISTED6 = 5;
|
|
6407
|
+
var describeFindings = (t, items) => {
|
|
6408
|
+
const listed3 = items.slice(0, MAX_LISTED6).map(({ ruleId, where }) => t("secrets.found_item", { rule: ruleId, where }));
|
|
6409
|
+
const rest = items.length - listed3.length;
|
|
6410
|
+
return rest > 0 ? `${listed3.join("; ")}; ${t("secrets.found_more", { count: rest })}` : listed3.join("; ");
|
|
6411
|
+
};
|
|
6412
|
+
var lineWhere = (t, finding) => t("secrets.where.line", { line: finding.line, column: finding.column });
|
|
6413
|
+
|
|
5335
6414
|
// src/secrets/guards/exposure.ts
|
|
5336
6415
|
var HOOK_ACTOR = "finitemonkey";
|
|
5337
6416
|
var liveFindings = (input, texts) => {
|
|
@@ -5376,7 +6455,7 @@ var secretsPostToolUse = (input) => {
|
|
|
5376
6455
|
};
|
|
5377
6456
|
|
|
5378
6457
|
// src/secrets/guards/pre-tool.ts
|
|
5379
|
-
import { relative as
|
|
6458
|
+
import { relative as relative6 } from "node:path";
|
|
5380
6459
|
|
|
5381
6460
|
// src/secrets/guards/paths.ts
|
|
5382
6461
|
var SAFE_ENV = /^\.env\.(?:example|sample|template|dist|defaults|schema)$/i;
|
|
@@ -5484,10 +6563,10 @@ var commitIntent = (segments) => {
|
|
|
5484
6563
|
// src/secrets/guards/shell.ts
|
|
5485
6564
|
var SEPARATORS2 = /\|\||&&|[|;&\n]/;
|
|
5486
6565
|
var TOKEN = /"((?:[^"\\]|\\.)*)"|'([^']*)'|(\S+)/g;
|
|
5487
|
-
var
|
|
6566
|
+
var ASSIGNMENT3 = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
5488
6567
|
var OUTPUT_REDIRECT = /^(?:\d|&)?>{1,2}\|?$/;
|
|
5489
6568
|
var OUTPUT_ATTACHED = /^(?:\d|&)?>{1,2}\|?\S/;
|
|
5490
|
-
var
|
|
6569
|
+
var WRAPPERS3 = /* @__PURE__ */ new Set(["sudo", "command", "exec", "time", "nice", "nohup", "builtin"]);
|
|
5491
6570
|
var tokensOf = (text6) => Array.from(text6.matchAll(TOKEN), (match) => match[1] ?? match[2] ?? match[3] ?? "");
|
|
5492
6571
|
var splitRedirects = (tokens) => {
|
|
5493
6572
|
const words = [];
|
|
@@ -5509,24 +6588,24 @@ var splitRedirects = (tokens) => {
|
|
|
5509
6588
|
}
|
|
5510
6589
|
return { words, inputs };
|
|
5511
6590
|
};
|
|
5512
|
-
var
|
|
6591
|
+
var segmentOf2 = (text6) => {
|
|
5513
6592
|
const { words, inputs } = splitRedirects(tokensOf(text6));
|
|
5514
6593
|
let index = 0;
|
|
5515
|
-
while (index < words.length && (
|
|
6594
|
+
while (index < words.length && (ASSIGNMENT3.test(words[index] ?? "") || WRAPPERS3.has(words[index] ?? ""))) index += 1;
|
|
5516
6595
|
const program = words[index];
|
|
5517
6596
|
if (program === void 0) return inputs.length > 0 ? { program: "", args: [], redirectedFrom: inputs } : void 0;
|
|
5518
6597
|
return { program: program.split(/[\\/]/).pop() ?? program, args: words.slice(index + 1), redirectedFrom: inputs };
|
|
5519
6598
|
};
|
|
5520
6599
|
var segmentsOf = (command) => command.split(SEPARATORS2).flatMap((part) => {
|
|
5521
|
-
const segment =
|
|
6600
|
+
const segment = segmentOf2(part);
|
|
5522
6601
|
return segment === void 0 ? [] : [segment];
|
|
5523
6602
|
});
|
|
5524
6603
|
|
|
5525
6604
|
// src/secrets/guards/pre-tool.ts
|
|
5526
|
-
var
|
|
6605
|
+
var ALLOW3 = { kind: "allow" };
|
|
5527
6606
|
var relativeFile = (cwd, file) => {
|
|
5528
6607
|
if (file === "") return file;
|
|
5529
|
-
const rel =
|
|
6608
|
+
const rel = relative6(cwd, file).split("\\").join("/");
|
|
5530
6609
|
return rel.startsWith("..") || rel === "" ? file : rel;
|
|
5531
6610
|
};
|
|
5532
6611
|
var writeDecision = (input, tool) => {
|
|
@@ -5541,7 +6620,7 @@ var writeDecision = (input, tool) => {
|
|
|
5541
6620
|
}));
|
|
5542
6621
|
return { kind: "block", reason: t("secrets.block.write", { file, found: describeFindings(t, items) }) };
|
|
5543
6622
|
}
|
|
5544
|
-
return
|
|
6623
|
+
return ALLOW3;
|
|
5545
6624
|
};
|
|
5546
6625
|
var bashMessage = (t, finding) => {
|
|
5547
6626
|
switch (finding.kind) {
|
|
@@ -5555,7 +6634,7 @@ var bashMessage = (t, finding) => {
|
|
|
5555
6634
|
};
|
|
5556
6635
|
var commitDecision = (input, includeUnstaged) => {
|
|
5557
6636
|
const staged = scanStaged(input.runtime, input.cwd, includeUnstaged);
|
|
5558
|
-
if (!staged.ok || staged.value.length === 0) return
|
|
6637
|
+
if (!staged.ok || staged.value.length === 0) return ALLOW3;
|
|
5559
6638
|
const items = staged.value.map((finding) => ({
|
|
5560
6639
|
ruleId: finding.ruleId,
|
|
5561
6640
|
where: input.t("secrets.where.file", { file: finding.file, line: finding.line, column: finding.column })
|
|
@@ -5564,7 +6643,7 @@ var commitDecision = (input, includeUnstaged) => {
|
|
|
5564
6643
|
};
|
|
5565
6644
|
var bashDecision = (input) => {
|
|
5566
6645
|
const command = optionalString(toolInput2(input.payload), "command") ?? "";
|
|
5567
|
-
if (command === "") return
|
|
6646
|
+
if (command === "") return ALLOW3;
|
|
5568
6647
|
const segments = segmentsOf(command);
|
|
5569
6648
|
const [first] = bashFindings(command, segments);
|
|
5570
6649
|
if (first !== void 0) return { kind: "block", reason: bashMessage(input.t, first) };
|
|
@@ -5574,23 +6653,23 @@ var bashDecision = (input) => {
|
|
|
5574
6653
|
return { kind: "block", reason: input.t("secrets.block.bash", { found: describeFindings(input.t, items) }) };
|
|
5575
6654
|
}
|
|
5576
6655
|
const commit2 = commitIntent(segments);
|
|
5577
|
-
return commit2 === void 0 ?
|
|
6656
|
+
return commit2 === void 0 ? ALLOW3 : commitDecision(input, commit2.includeUnstaged);
|
|
5578
6657
|
};
|
|
5579
6658
|
var readDecision = (input) => {
|
|
5580
6659
|
const inputs = toolInput2(input.payload);
|
|
5581
6660
|
const target = optionalString(inputs, "file_path") ?? optionalString(inputs, "path") ?? "";
|
|
5582
|
-
if (target === "" || !isSensitivePath(target)) return
|
|
6661
|
+
if (target === "" || !isSensitivePath(target)) return ALLOW3;
|
|
5583
6662
|
return { kind: "block", reason: input.t("secrets.block.read", { target: relativeFile(input.cwd, target) }) };
|
|
5584
6663
|
};
|
|
5585
6664
|
var WRITERS2 = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
5586
|
-
var
|
|
6665
|
+
var SHELLS4 = /* @__PURE__ */ new Set(["Bash", "PowerShell"]);
|
|
5587
6666
|
var READS = /* @__PURE__ */ new Set(["Read", "Grep"]);
|
|
5588
6667
|
var secretsPreToolUse = (input) => {
|
|
5589
6668
|
const tool = toolName2(input.payload);
|
|
5590
6669
|
if (WRITERS2.has(tool)) return writeDecision(input, tool);
|
|
5591
|
-
if (
|
|
6670
|
+
if (SHELLS4.has(tool)) return bashDecision(input);
|
|
5592
6671
|
if (READS.has(tool)) return readDecision(input);
|
|
5593
|
-
return
|
|
6672
|
+
return ALLOW3;
|
|
5594
6673
|
};
|
|
5595
6674
|
|
|
5596
6675
|
// src/hooks/handlers.ts
|
|
@@ -5648,7 +6727,7 @@ var SECRETS_HANDLERS = {
|
|
|
5648
6727
|
UserPromptSubmit: [secretsUserPromptSubmit],
|
|
5649
6728
|
PostToolUse: [secretsPostToolUse]
|
|
5650
6729
|
};
|
|
5651
|
-
var DEFAULT_HANDLERS = mergeHandlers(SECRETS_HANDLERS, PORTAO_HANDLERS);
|
|
6730
|
+
var DEFAULT_HANDLERS = mergeHandlers(SECRETS_HANDLERS, PORTAO_HANDLERS, PROSE_HANDLERS);
|
|
5652
6731
|
|
|
5653
6732
|
// src/hooks/payload.ts
|
|
5654
6733
|
var EMPTY_PAYLOAD = {
|
|
@@ -5795,28 +6874,6 @@ var runHook = async (event, options) => {
|
|
|
5795
6874
|
// src/install/plugin-yield.ts
|
|
5796
6875
|
import { join as join14 } from "node:path";
|
|
5797
6876
|
|
|
5798
|
-
// src/install/layout.ts
|
|
5799
|
-
var STATE_DIR = ".finitemonkey";
|
|
5800
|
-
var SETTINGS_FILE = ".claude/settings.json";
|
|
5801
|
-
var GITIGNORE_FILE = ".gitignore";
|
|
5802
|
-
var CONFIG_FILE2 = ".finitemonkey/config.yml";
|
|
5803
|
-
var LOCK_FILE = ".finitemonkey/lock.json";
|
|
5804
|
-
var BACKUPS_DIR = ".finitemonkey/backups";
|
|
5805
|
-
var LOCKS_DIR = ".finitemonkey/locks";
|
|
5806
|
-
var INSTALL_LOCK = ".finitemonkey/locks/install.lock";
|
|
5807
|
-
var LOCAL_BIN = "node_modules/finitemonkey/dist/finitemonkey.mjs";
|
|
5808
|
-
var VENDOR_BIN = ".finitemonkey/runtime/dist/finitemonkey.mjs";
|
|
5809
|
-
var VENDOR_PACKAGE = ".finitemonkey/runtime/package.json";
|
|
5810
|
-
var LOCK_SCHEMA = "finitemonkey/lock@1";
|
|
5811
|
-
var PLAN_SCHEMA = "finitemonkey/install-plan@1";
|
|
5812
|
-
var GENERATED_MARKER = "finitemonkey:generated v=1";
|
|
5813
|
-
var BLOCK_BEGIN = "# finitemonkey:begin v=1";
|
|
5814
|
-
var BLOCK_END = "# finitemonkey:end";
|
|
5815
|
-
var INSTALL_LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
5816
|
-
var PRESETS = ["neutral", "gabriel"];
|
|
5817
|
-
var parsePreset = (raw) => raw === void 0 ? "neutral" : PRESETS.find((preset) => preset === raw);
|
|
5818
|
-
var COMMITTED_STATE = ["config.yml", "runtime/", "tasks/", "rules.yml", "policy.yml", "findings/"];
|
|
5819
|
-
|
|
5820
6877
|
// src/install/hook-entries.ts
|
|
5821
6878
|
var INSTALLED_HOOKS = [
|
|
5822
6879
|
{ event: "SessionStart", timeout: 30, guard: false },
|
|
@@ -6101,12 +7158,12 @@ var loadCatalogs = (ctx, dir) => {
|
|
|
6101
7158
|
if (!es.ok) return es;
|
|
6102
7159
|
return accept({ en: en.value, "pt-BR": ptBR.value, es: es.value });
|
|
6103
7160
|
};
|
|
6104
|
-
var describeProblem = (ctx,
|
|
6105
|
-
const kind = ctx.t(`i18n.problem.${
|
|
6106
|
-
expected: (
|
|
6107
|
-
actual: (
|
|
7161
|
+
var describeProblem = (ctx, problem4) => {
|
|
7162
|
+
const kind = ctx.t(`i18n.problem.${problem4.kind}`, {
|
|
7163
|
+
expected: (problem4.expected ?? []).join(", "),
|
|
7164
|
+
actual: (problem4.actual ?? []).join(", ")
|
|
6108
7165
|
});
|
|
6109
|
-
return ` ${
|
|
7166
|
+
return ` ${problem4.lang} ${problem4.key} ${kind}`;
|
|
6110
7167
|
};
|
|
6111
7168
|
var runI18nCheck = (ctx, dir) => {
|
|
6112
7169
|
const input = dir === void 0 ? accept(catalogs) : loadCatalogs(ctx, dir);
|
|
@@ -6119,7 +7176,7 @@ var runI18nCheck = (ctx, dir) => {
|
|
|
6119
7176
|
ok: false,
|
|
6120
7177
|
exitCode: ExitCode.CheckFailed,
|
|
6121
7178
|
data,
|
|
6122
|
-
human: [summary, ...report2.problems.map((
|
|
7179
|
+
human: [summary, ...report2.problems.map((problem4) => describeProblem(ctx, problem4))],
|
|
6123
7180
|
error: {
|
|
6124
7181
|
code: "i18n_mismatch",
|
|
6125
7182
|
message: ctx.t("error.i18n_mismatch", { count: report2.problems.length }),
|
|
@@ -6471,7 +7528,7 @@ var generatedItem = (project, file, previous) => {
|
|
|
6471
7528
|
return { ...base, action: actionFor(before, template, "update"), after: template };
|
|
6472
7529
|
};
|
|
6473
7530
|
var generatedFiles = (input, vendor) => {
|
|
6474
|
-
const config = { path:
|
|
7531
|
+
const config = { path: CONFIG_FILE, template: configTemplate(input.t, input.preset, input.lang), isOurs: hasMarker };
|
|
6475
7532
|
if (!vendor) return accept([config]);
|
|
6476
7533
|
if (input.bundle === void 0) return reject({ kind: "bundle", detail: "the running finitemonkey is not a bundle" });
|
|
6477
7534
|
return accept([config, ...vendoredFiles(input.bundle, input.version)]);
|
|
@@ -6496,11 +7553,11 @@ var needsWrite = (plan) => hasChanges(plan) || plan.previous === void 0;
|
|
|
6496
7553
|
|
|
6497
7554
|
// src/install/apply.ts
|
|
6498
7555
|
var stamp = (iso) => iso.replace(/[:.]/g, "-");
|
|
6499
|
-
var ensureParent = (project,
|
|
6500
|
-
var writeText = (project,
|
|
6501
|
-
const parent = ensureParent(project,
|
|
6502
|
-
const written = parent.ok ? project.runtime.writeAtomic(project.abs(
|
|
6503
|
-
return written.ok ? accept(void 0) : reject({ path:
|
|
7556
|
+
var ensureParent = (project, relative8) => project.runtime.mkdir(project.abs(dirname4(relative8)));
|
|
7557
|
+
var writeText = (project, relative8, text6) => {
|
|
7558
|
+
const parent = ensureParent(project, relative8);
|
|
7559
|
+
const written = parent.ok ? project.runtime.writeAtomic(project.abs(relative8), text6) : parent;
|
|
7560
|
+
return written.ok ? accept(void 0) : reject({ path: relative8, detail: written.error });
|
|
6504
7561
|
};
|
|
6505
7562
|
var restore = (project, items) => {
|
|
6506
7563
|
for (const item of items) {
|
|
@@ -6633,9 +7690,9 @@ var lineDiff = (path, before, after) => {
|
|
|
6633
7690
|
const changes = walk(splitLines(before), splitLines(after)).filter(([op]) => op !== "=");
|
|
6634
7691
|
if (changes.length === 0) return [];
|
|
6635
7692
|
const lines2 = changes.map(([op, line]) => `${op} ${line}`);
|
|
6636
|
-
const
|
|
6637
|
-
const hidden = lines2.length -
|
|
6638
|
-
return [`--- ${path}`, `+++ ${path}`, ...
|
|
7693
|
+
const shown2 = lines2.slice(0, MAX_DIFF_LINES);
|
|
7694
|
+
const hidden = lines2.length - shown2.length;
|
|
7695
|
+
return [`--- ${path}`, `+++ ${path}`, ...shown2, ...hidden > 0 ? [`… ${String(hidden)} more`] : []];
|
|
6639
7696
|
};
|
|
6640
7697
|
|
|
6641
7698
|
// src/install/report.ts
|
|
@@ -6695,17 +7752,17 @@ var planLines = (t, data, withDiff) => [
|
|
|
6695
7752
|
// src/install/project.ts
|
|
6696
7753
|
import { dirname as dirname5, join as join17 } from "node:path";
|
|
6697
7754
|
var projectAt = (root, runtime) => {
|
|
6698
|
-
const abs = (
|
|
7755
|
+
const abs = (relative8) => toAbsolutePath(join17(root, ...relative8.split("/")));
|
|
6699
7756
|
return {
|
|
6700
7757
|
root,
|
|
6701
7758
|
runtime,
|
|
6702
7759
|
abs,
|
|
6703
|
-
read: (
|
|
6704
|
-
const text6 = runtime.readText(abs(
|
|
7760
|
+
read: (relative8) => {
|
|
7761
|
+
const text6 = runtime.readText(abs(relative8));
|
|
6705
7762
|
return text6.ok ? text6.value : void 0;
|
|
6706
7763
|
},
|
|
6707
|
-
exists: (
|
|
6708
|
-
isDir: (
|
|
7764
|
+
exists: (relative8) => runtime.stat(abs(relative8)) !== void 0,
|
|
7765
|
+
isDir: (relative8) => runtime.stat(abs(relative8))?.kind === "dir"
|
|
6709
7766
|
};
|
|
6710
7767
|
};
|
|
6711
7768
|
var findGitRoot = (start, runtime) => {
|
|
@@ -6804,8 +7861,8 @@ var presentPlan = (ctx, request, plan) => {
|
|
|
6804
7861
|
if (ctx.dryRun) return success(data, [...human2, ctx.t("install.dry_run")]);
|
|
6805
7862
|
if (!needsWrite(plan)) return success(data, [...human2, ctx.t("install.nothing_to_do")]);
|
|
6806
7863
|
const next = flagsFor(request, plan.preset);
|
|
6807
|
-
const
|
|
6808
|
-
return { ...failureFrom(ctx.t, { ...
|
|
7864
|
+
const problem4 = confirmProblem(request.mode, ctx.t("install.confirm_question"), next);
|
|
7865
|
+
return { ...failureFrom(ctx.t, { ...problem4, data }, human2), next };
|
|
6809
7866
|
};
|
|
6810
7867
|
var applyAndReport = (ctx, request, plan) => {
|
|
6811
7868
|
const project = openProject(ctx).project;
|
|
@@ -6848,16 +7905,136 @@ var initCommand = {
|
|
|
6848
7905
|
run: (ctx, args) => Promise.resolve(runInstall(ctx, initRequest(args), "neutral"))
|
|
6849
7906
|
};
|
|
6850
7907
|
|
|
6851
|
-
// src/
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
7908
|
+
// src/commands/prose-check.ts
|
|
7909
|
+
import { relative as relative7 } from "node:path";
|
|
7910
|
+
|
|
7911
|
+
// src/commands/prose-view.ts
|
|
7912
|
+
var PROSE_CHECK_SCHEMA = "finitemonkey/prose-check@1";
|
|
7913
|
+
var spanView = (span) => ({
|
|
7914
|
+
startLine: span.startLine,
|
|
7915
|
+
endLine: span.endLine,
|
|
7916
|
+
score: span.score,
|
|
7917
|
+
rules: rulesOf2([span]),
|
|
7918
|
+
hits: span.hits.map(({ rule, line, weight, excerpt }) => ({ rule, line, weight, excerpt }))
|
|
7919
|
+
});
|
|
7920
|
+
var proseCheckData = (reports) => {
|
|
7921
|
+
const files = reports.map(({ file, report: report2 }) => ({
|
|
7922
|
+
file,
|
|
7923
|
+
allowed: report2.allowed,
|
|
7924
|
+
spans: report2.spans.map(spanView)
|
|
7925
|
+
}));
|
|
7926
|
+
return {
|
|
7927
|
+
schema: PROSE_CHECK_SCHEMA,
|
|
7928
|
+
files,
|
|
7929
|
+
summary: {
|
|
7930
|
+
files: files.length,
|
|
7931
|
+
flagged: files.filter((file) => file.spans.length > 0).length,
|
|
7932
|
+
spans: files.reduce((sum, file) => sum + file.spans.length, 0)
|
|
7933
|
+
}
|
|
7934
|
+
};
|
|
6856
7935
|
};
|
|
6857
|
-
var
|
|
6858
|
-
|
|
6859
|
-
const
|
|
6860
|
-
|
|
7936
|
+
var fileLines = (t, report2) => {
|
|
7937
|
+
if (report2.report.allowed) return [t("prose.check.allowed", { file: report2.file, marker: ALLOW_FILE_COMMENT })];
|
|
7938
|
+
const { spans } = report2.report;
|
|
7939
|
+
if (spans.length === 0) return [];
|
|
7940
|
+
return [
|
|
7941
|
+
t("prose.check.file", { file: report2.file, count: spans.length }),
|
|
7942
|
+
...spans.flatMap((span) => [
|
|
7943
|
+
t("prose.check.span", {
|
|
7944
|
+
file: report2.file,
|
|
7945
|
+
start: span.startLine,
|
|
7946
|
+
end: span.endLine,
|
|
7947
|
+
score: span.score,
|
|
7948
|
+
rules: spanRules(span)
|
|
7949
|
+
}),
|
|
7950
|
+
...rulesOf2([span]).map((rule) => t("prose.check.fix", { fix: `${rule}: ${t(RULE_FIX_KEYS[rule])}` }))
|
|
7951
|
+
])
|
|
7952
|
+
];
|
|
7953
|
+
};
|
|
7954
|
+
var proseCheckHuman = (t, reports, data) => [
|
|
7955
|
+
...reports.flatMap((report2) => fileLines(t, report2)),
|
|
7956
|
+
...data.summary.spans === 0 ? [t("prose.check.clean", { count: data.summary.files })] : []
|
|
7957
|
+
];
|
|
7958
|
+
|
|
7959
|
+
// src/commands/prose-check.ts
|
|
7960
|
+
var TEXT_FILE = /\.(?:md|markdown|mdx|txt)$/i;
|
|
7961
|
+
var STDIN_LABEL = "<stdin>";
|
|
7962
|
+
var problem2 = (code, messageKey, params2) => ({
|
|
7963
|
+
code,
|
|
7964
|
+
messageKey,
|
|
7965
|
+
params: params2,
|
|
7966
|
+
fix: "finitemonkey help prose check",
|
|
7967
|
+
exitCode: ExitCode.Usage
|
|
7968
|
+
});
|
|
7969
|
+
var underRoot = (root, path) => toPosixPath(relative7(root, path));
|
|
7970
|
+
var expand = (ctx, root, target) => {
|
|
7971
|
+
const path = toAbsolutePath(target, ctx.cwd);
|
|
7972
|
+
const stat = ctx.runtime.stat(path);
|
|
7973
|
+
if (stat === void 0)
|
|
7974
|
+
return reject(problem2("prose_unreadable", "prose.error.unreadable", { file: target, detail: "not found" }));
|
|
7975
|
+
if (stat.kind === "file") return accept([underRoot(root, path)]);
|
|
7976
|
+
const walked = ctx.runtime.walkFiles(path);
|
|
7977
|
+
if (!walked.ok)
|
|
7978
|
+
return reject(problem2("prose_unreadable", "prose.error.unreadable", { file: target, detail: walked.error }));
|
|
7979
|
+
return accept(
|
|
7980
|
+
walked.value.map((name) => underRoot(root, toAbsolutePath(toPosixPath(name), path))).filter((name) => TEXT_FILE.test(name) && !/(?:^|\/)(?:node_modules|\.git)\//.test(name)).sort()
|
|
7981
|
+
);
|
|
7982
|
+
};
|
|
7983
|
+
var targetsOf = (ctx, root, paths) => {
|
|
7984
|
+
if (paths.length === 0) {
|
|
7985
|
+
const changed = changedMarkdown(ctx.runtime, root);
|
|
7986
|
+
return changed.ok ? changed : reject(problem2("prose_no_input", "prose.error.no_input", {}));
|
|
7987
|
+
}
|
|
7988
|
+
const files = [];
|
|
7989
|
+
for (const path of paths) {
|
|
7990
|
+
const expanded = expand(ctx, root, path);
|
|
7991
|
+
if (!expanded.ok) return expanded;
|
|
7992
|
+
files.push(...expanded.value);
|
|
7993
|
+
}
|
|
7994
|
+
return accept([...new Set(files)]);
|
|
7995
|
+
};
|
|
7996
|
+
var reportsOf = async (ctx, root, paths, mode) => {
|
|
7997
|
+
const forced = mode.record ? "record" : void 0;
|
|
7998
|
+
if (mode.stdin) {
|
|
7999
|
+
const report2 = scoreProse(await readAll(ctx.io.stdin), { context: forced ?? "docs" });
|
|
8000
|
+
return accept([{ file: STDIN_LABEL, report: report2 }]);
|
|
8001
|
+
}
|
|
8002
|
+
const targets2 = targetsOf(ctx, root, paths);
|
|
8003
|
+
if (!targets2.ok) return targets2;
|
|
8004
|
+
const scored = scoreFiles(ctx.runtime, root, targets2.value, forced);
|
|
8005
|
+
return scored.ok ? scored : reject(problem2("prose_unreadable", "prose.error.unreadable", { file: "", detail: scored.error }));
|
|
8006
|
+
};
|
|
8007
|
+
var resultOf = (ctx, reports) => {
|
|
8008
|
+
const data = proseCheckData(reports);
|
|
8009
|
+
const human2 = proseCheckHuman(ctx.t, reports, data);
|
|
8010
|
+
if (data.summary.spans === 0) return success(data, human2);
|
|
8011
|
+
return failureFrom(
|
|
8012
|
+
ctx.t,
|
|
8013
|
+
{
|
|
8014
|
+
code: "prose_found",
|
|
8015
|
+
messageKey: "prose.error.found",
|
|
8016
|
+
params: { count: data.summary.spans },
|
|
8017
|
+
fix: `rewrite the listed passages; for deliberate brand text, the person adds ${ALLOW_COMMENT}`,
|
|
8018
|
+
exitCode: ExitCode.CheckFailed,
|
|
8019
|
+
data
|
|
8020
|
+
},
|
|
8021
|
+
human2
|
|
8022
|
+
);
|
|
8023
|
+
};
|
|
8024
|
+
var proseCheckCommand = {
|
|
8025
|
+
path: ["prose", "check"],
|
|
8026
|
+
summaryKey: "prose.command.check",
|
|
8027
|
+
usage: "finitemonkey prose check [paths...] [--stdin] [--record] [--json]",
|
|
8028
|
+
status: "ready",
|
|
8029
|
+
options: { stdin: { type: "boolean" }, record: { type: "boolean" } },
|
|
8030
|
+
maxPositionals: 1e3,
|
|
8031
|
+
run: async (ctx, args) => {
|
|
8032
|
+
const root = projectRoot(ctx);
|
|
8033
|
+
const mode = { stdin: args.values["stdin"] === true, record: args.values["record"] === true };
|
|
8034
|
+
const reports = await reportsOf(ctx, root, args.positionals, mode);
|
|
8035
|
+
if (!reports.ok) return failureFrom(ctx.t, reports.error);
|
|
8036
|
+
return resultOf(ctx, reports.value);
|
|
8037
|
+
}
|
|
6861
8038
|
};
|
|
6862
8039
|
|
|
6863
8040
|
// src/commands/secrets-problems.ts
|
|
@@ -6892,7 +8069,7 @@ var daysOf = (values) => {
|
|
|
6892
8069
|
return Number.isInteger(days) && days > 0 && days <= EXCEPTION_MAX_DAYS ? days : void 0;
|
|
6893
8070
|
};
|
|
6894
8071
|
var plusDays = (iso, days) => new Date(Date.parse(iso) + days * DAY_MS).toISOString();
|
|
6895
|
-
var failure = (ctx,
|
|
8072
|
+
var failure = (ctx, problem4) => failureFrom(ctx.t, problem4);
|
|
6896
8073
|
var inputProblem = (ctx, id, reason, days) => {
|
|
6897
8074
|
const fix = "finitemonkey secrets accept <id> --reason <why> [--days 1-90] [--false-positive]";
|
|
6898
8075
|
if (!isPerson(ctx)) {
|
|
@@ -6967,12 +8144,12 @@ var secretsAcceptCommand = {
|
|
|
6967
8144
|
const id = args.positionals[0] ?? "";
|
|
6968
8145
|
const reason = stringOption(args.values, "reason");
|
|
6969
8146
|
const days = daysOf(args.values);
|
|
6970
|
-
const
|
|
6971
|
-
if (
|
|
8147
|
+
const problem4 = inputProblem(ctx, id, reason, days);
|
|
8148
|
+
if (problem4 !== void 0 || reason === void 0 || days === void 0) {
|
|
6972
8149
|
return Promise.resolve(
|
|
6973
8150
|
failure(
|
|
6974
8151
|
ctx,
|
|
6975
|
-
|
|
8152
|
+
problem4 ?? usageProblem("invalid_arguments", "secrets.error.reason_required", "finitemonkey help secrets accept")
|
|
6976
8153
|
)
|
|
6977
8154
|
);
|
|
6978
8155
|
}
|
|
@@ -6993,7 +8170,7 @@ var PURGE_STEPS = [
|
|
|
6993
8170
|
"secrets.purge.step4",
|
|
6994
8171
|
"secrets.purge.step5"
|
|
6995
8172
|
];
|
|
6996
|
-
var fail2 = (ctx,
|
|
8173
|
+
var fail2 = (ctx, problem4) => failureFrom(ctx.t, problem4);
|
|
6997
8174
|
var notFound2 = (ctx, id) => fail2(
|
|
6998
8175
|
ctx,
|
|
6999
8176
|
usageProblem("secrets_incident_not_found", "secrets.error.incident_not_found", `finitemonkey secrets incident`, {
|
|
@@ -7154,7 +8331,7 @@ var humanOf = (ctx, data) => [
|
|
|
7154
8331
|
),
|
|
7155
8332
|
...data.summary.total === 0 ? [ctx.t("secrets.scan.clean")] : []
|
|
7156
8333
|
];
|
|
7157
|
-
var
|
|
8334
|
+
var resultOf2 = (ctx, data) => {
|
|
7158
8335
|
const human2 = humanOf(ctx, data);
|
|
7159
8336
|
const failing = data.summary.new + data.summary.expired;
|
|
7160
8337
|
if (failing === 0) return success(data, human2);
|
|
@@ -7198,7 +8375,7 @@ var secretsScanCommand = {
|
|
|
7198
8375
|
);
|
|
7199
8376
|
if (mode === "path") prune(ctx, root, baseline.value, outcome);
|
|
7200
8377
|
const incidents = mode === "history" ? historyIncidents(ctx, root, outcome.fresh) : [];
|
|
7201
|
-
return
|
|
8378
|
+
return resultOf2(ctx, dataOf(mode, outcome, incidents));
|
|
7202
8379
|
}
|
|
7203
8380
|
};
|
|
7204
8381
|
|
|
@@ -7286,11 +8463,11 @@ var explicitChecks = (draft) => [
|
|
|
7286
8463
|
];
|
|
7287
8464
|
var draftChecks = (runtime, paths, draft) => {
|
|
7288
8465
|
const disk = {
|
|
7289
|
-
read: (
|
|
7290
|
-
const text6 = runtime.readText(toAbsolutePath(join18(paths.root,
|
|
8466
|
+
read: (relative8) => {
|
|
8467
|
+
const text6 = runtime.readText(toAbsolutePath(join18(paths.root, relative8)));
|
|
7291
8468
|
return text6.ok ? text6.value : void 0;
|
|
7292
8469
|
},
|
|
7293
|
-
exists: (
|
|
8470
|
+
exists: (relative8) => runtime.stat(toAbsolutePath(join18(paths.root, relative8))) !== void 0
|
|
7294
8471
|
};
|
|
7295
8472
|
const detected = detectChecks(disk);
|
|
7296
8473
|
const base = draft.runs.length > 0 || draft.tests !== void 0 ? explicitChecks(draft) : detected;
|
|
@@ -7300,6 +8477,7 @@ var draftChecks = (runtime, paths, draft) => {
|
|
|
7300
8477
|
...hasTests ? [] : [{ id: "tests", kind: "tests" }],
|
|
7301
8478
|
{ id: "placeholders", kind: "placeholders" },
|
|
7302
8479
|
{ id: "secrets", kind: "secrets" },
|
|
8480
|
+
{ id: "prose", kind: "prose" },
|
|
7303
8481
|
...draft.paths.length > 0 || draft.budget !== void 0 ? [{ id: "diff", kind: "diff" }] : [],
|
|
7304
8482
|
...draft.git.length > 0 ? [{ id: "git", kind: "git", require: draft.git }] : [],
|
|
7305
8483
|
{ id: "pr", kind: "pr", requireOpen: draft.git.includes("pr_open") },
|
|
@@ -7342,7 +8520,7 @@ var createTask = (runtime, paths, draft) => {
|
|
|
7342
8520
|
};
|
|
7343
8521
|
|
|
7344
8522
|
// src/commands/task.ts
|
|
7345
|
-
var
|
|
8523
|
+
var problem3 = (code, messageKey, fix, params2 = {}, exitCode = ExitCode.Usage) => ({
|
|
7346
8524
|
code,
|
|
7347
8525
|
messageKey,
|
|
7348
8526
|
params: params2,
|
|
@@ -7353,7 +8531,7 @@ var projectOf = (ctx) => {
|
|
|
7353
8531
|
const root = gitRoot(gitAt(ctx.runtime, ctx.cwd));
|
|
7354
8532
|
return root === void 0 ? void 0 : fmPaths(root);
|
|
7355
8533
|
};
|
|
7356
|
-
var noRepo = (ctx) => failureFrom(ctx.t,
|
|
8534
|
+
var noRepo = (ctx) => failureFrom(ctx.t, problem3("not_a_git_repo", "error.not_a_git_repo", "git init", {}, ExitCode.NotConfigured));
|
|
7357
8535
|
var list3 = (values, name) => {
|
|
7358
8536
|
const value = values[name];
|
|
7359
8537
|
const raw = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
@@ -7385,17 +8563,17 @@ var createFailure = (ctx, failure2) => {
|
|
|
7385
8563
|
if (failure2.kind === "exists") {
|
|
7386
8564
|
return failureFrom(
|
|
7387
8565
|
ctx.t,
|
|
7388
|
-
|
|
8566
|
+
problem3("task_exists", "error.task_exists", `finitemonkey task show ${failure2.id}`, { task: failure2.id })
|
|
7389
8567
|
);
|
|
7390
8568
|
}
|
|
7391
8569
|
const detail = failure2.kind === "invalid" ? failure2.issues.join("; ") : failure2.detail;
|
|
7392
|
-
return failureFrom(ctx.t,
|
|
8570
|
+
return failureFrom(ctx.t, problem3("task_invalid", "error.task_invalid", "finitemonkey help task new", { detail }));
|
|
7393
8571
|
};
|
|
7394
8572
|
var newTask = (ctx, id, values) => {
|
|
7395
8573
|
if (id === void 0)
|
|
7396
8574
|
return failureFrom(
|
|
7397
8575
|
ctx.t,
|
|
7398
|
-
|
|
8576
|
+
problem3("invalid_arguments", "error.invalid_arguments", "finitemonkey help task new", { detail: "missing <id>" })
|
|
7399
8577
|
);
|
|
7400
8578
|
const paths = projectOf(ctx);
|
|
7401
8579
|
if (paths === void 0) return noRepo(ctx);
|
|
@@ -7421,7 +8599,7 @@ var showTask = (ctx, id) => {
|
|
|
7421
8599
|
if (report2 === void 0) {
|
|
7422
8600
|
return failureFrom(
|
|
7423
8601
|
ctx.t,
|
|
7424
|
-
|
|
8602
|
+
problem3(
|
|
7425
8603
|
"task_not_found",
|
|
7426
8604
|
"error.task_not_found",
|
|
7427
8605
|
"finitemonkey task new <id> --goal <text>",
|
|
@@ -7531,9 +8709,9 @@ var applyItem = (project, item) => {
|
|
|
7531
8709
|
if (item.after === void 0 || item.action === "gone" || item.action.startsWith("keep")) return accept(void 0);
|
|
7532
8710
|
return writeText(project, item.path, item.after);
|
|
7533
8711
|
};
|
|
7534
|
-
var isEmptyDir = (project,
|
|
7535
|
-
const
|
|
7536
|
-
return
|
|
8712
|
+
var isEmptyDir = (project, relative8) => {
|
|
8713
|
+
const listed3 = project.runtime.listDir(project.abs(relative8));
|
|
8714
|
+
return listed3.ok && listed3.value.length === 0;
|
|
7537
8715
|
};
|
|
7538
8716
|
var finish = (project, plan) => {
|
|
7539
8717
|
const target = plan.purge ? STATE_DIR : LOCK_FILE;
|
|
@@ -7588,8 +8766,8 @@ var runUninstall = (ctx, args) => {
|
|
|
7588
8766
|
if (ctx.dryRun) return success(preview, [...linesOf(ctx, preview), ctx.t("install.dry_run")]);
|
|
7589
8767
|
if (!ctx.yes) {
|
|
7590
8768
|
const next = fixFor(args);
|
|
7591
|
-
const
|
|
7592
|
-
return { ...failureFrom(ctx.t, { ...
|
|
8769
|
+
const problem4 = confirmProblem("uninstall", ctx.t("install.uninstall.question"), next);
|
|
8770
|
+
return { ...failureFrom(ctx.t, { ...problem4, data: preview }, linesOf(ctx, preview)), next };
|
|
7593
8771
|
}
|
|
7594
8772
|
const removed = applyRemoval(project, plan);
|
|
7595
8773
|
if (!removed.ok) return failureFrom(ctx.t, applyProblem(removed.error));
|
|
@@ -7672,6 +8850,7 @@ var REGISTRY = [
|
|
|
7672
8850
|
secretsScanCommand,
|
|
7673
8851
|
secretsAcceptCommand,
|
|
7674
8852
|
secretsIncidentCommand,
|
|
8853
|
+
proseCheckCommand,
|
|
7675
8854
|
versionCommand,
|
|
7676
8855
|
helpCommand
|
|
7677
8856
|
];
|
|
@@ -7711,9 +8890,9 @@ var unknownCommand = (registry, attempted) => {
|
|
|
7711
8890
|
suggestion
|
|
7712
8891
|
});
|
|
7713
8892
|
};
|
|
7714
|
-
var failWith = (ctx, name,
|
|
8893
|
+
var failWith = (ctx, name, problem4) => ({
|
|
7715
8894
|
name,
|
|
7716
|
-
execute: () => Promise.resolve(failureFrom(ctx.t,
|
|
8895
|
+
execute: () => Promise.resolve(failureFrom(ctx.t, problem4))
|
|
7717
8896
|
});
|
|
7718
8897
|
var invoke = (ctx, argv, spec, registry) => {
|
|
7719
8898
|
const name = commandName(spec);
|
|
@@ -7798,11 +8977,11 @@ var run = async (options) => {
|
|
|
7798
8977
|
let result;
|
|
7799
8978
|
try {
|
|
7800
8979
|
if (loose.lang !== void 0 && normalizeLang(loose.lang) === void 0) {
|
|
7801
|
-
const
|
|
8980
|
+
const problem4 = usageProblem("invalid_lang", "error.invalid_lang", "finitemonkey --lang en", {
|
|
7802
8981
|
lang: loose.lang,
|
|
7803
8982
|
languages: LANGUAGES.join(", ")
|
|
7804
8983
|
});
|
|
7805
|
-
result = failureFrom(t,
|
|
8984
|
+
result = failureFrom(t, problem4);
|
|
7806
8985
|
} else {
|
|
7807
8986
|
const dispatched = dispatch(ctx, options.argv, loose, options.registry ?? REGISTRY);
|
|
7808
8987
|
name = dispatched.name;
|
|
@@ -7819,8 +8998,8 @@ var run = async (options) => {
|
|
|
7819
8998
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7820
8999
|
var UNKNOWN_VERSION = "0.0.0-unknown";
|
|
7821
9000
|
var VERSION_SOURCES = ["../package.json", "../.claude-plugin/plugin.json"];
|
|
7822
|
-
var versionAt = (readText, moduleUrl,
|
|
7823
|
-
const text6 = readText(toAbsolutePath(fileURLToPath3(new URL(
|
|
9001
|
+
var versionAt = (readText, moduleUrl, relative8) => {
|
|
9002
|
+
const text6 = readText(toAbsolutePath(fileURLToPath3(new URL(relative8, moduleUrl))));
|
|
7824
9003
|
if (!text6.ok) return void 0;
|
|
7825
9004
|
const parsed = parseJson(text6.value);
|
|
7826
9005
|
if (!parsed.ok || !isRecord(parsed.value)) return void 0;
|
|
@@ -7828,8 +9007,8 @@ var versionAt = (readText, moduleUrl, relative6) => {
|
|
|
7828
9007
|
return typeof version === "string" ? version : void 0;
|
|
7829
9008
|
};
|
|
7830
9009
|
var readPackageVersion = (readText, moduleUrl) => {
|
|
7831
|
-
for (const
|
|
7832
|
-
const version = versionAt(readText, moduleUrl,
|
|
9010
|
+
for (const relative8 of VERSION_SOURCES) {
|
|
9011
|
+
const version = versionAt(readText, moduleUrl, relative8);
|
|
7833
9012
|
if (version !== void 0) return version;
|
|
7834
9013
|
}
|
|
7835
9014
|
return UNKNOWN_VERSION;
|