finitemonkey 0.1.0-next.8 → 0.1.0-next.9

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.
@@ -4441,11 +4441,11 @@ var requirement2 = (want, ctx) => {
4441
4441
  };
4442
4442
  var gitCheck = (check2, ctx) => {
4443
4443
  const wants = check2.require ?? ["committed"];
4444
- const missing = wants.flatMap((want) => {
4444
+ const missing2 = wants.flatMap((want) => {
4445
4445
  const problem4 = requirement2(want, ctx);
4446
4446
  return problem4 === void 0 ? [] : [problem4];
4447
4447
  });
4448
- return missing.length > 0 ? failed(missing) : passed([note("gate.git.ok", { require: wants.join(", ") })]);
4448
+ return missing2.length > 0 ? failed(missing2) : passed([note("gate.git.ok", { require: wants.join(", ") })]);
4449
4449
  };
4450
4450
 
4451
4451
  // src/guards/release/grants.ts
@@ -6507,10 +6507,10 @@ var symbolFindings = (context, pair, path, text6) => {
6507
6507
  var verifyFile = (context, pair) => {
6508
6508
  const path = resultPathOf(context, pair);
6509
6509
  if (pair.ours?.binary === true || pair.theirs?.binary === true) return binaryFindings(context, pair, path);
6510
- const missing = ["ours", "theirs"].flatMap(
6510
+ const missing2 = ["ours", "theirs"].flatMap(
6511
6511
  (side) => created2(pair[side]) && path === void 0 ? [finding("missing-file", pair.key, side, pair.key)] : []
6512
6512
  );
6513
- if (missing.length > 0) return missing;
6513
+ if (missing2.length > 0) return missing2;
6514
6514
  const copied = [pair.ours, pair.theirs].find((diff) => diff?.status === "copied");
6515
6515
  const baseSource = copied?.oldPath ?? (created2(pair.ours) || created2(pair.theirs) ? void 0 : pair.key);
6516
6516
  const baseText = baseSource === void 0 ? void 0 : blobText(context.runtime, context.repo, context.inputs.base, baseSource);
@@ -10817,34 +10817,49 @@ var mergeHooks = (hooks, isOwn, wanted) => {
10817
10817
  var withHooks = (settings, hooks) => {
10818
10818
  return setKey(settings, "hooks", Object.keys(hooks).length === 0 ? void 0 : hooks);
10819
10819
  };
10820
- var withPermissions = (settings, deny2) => {
10820
+ var BLOCK_READS = "blockReadsOutsideWorkingDirectories";
10821
+ var withPermissions = (settings, lists) => {
10821
10822
  const permissions = asRecord(settings["permissions"]);
10822
- const next2 = setKey(permissions, "deny", deny2.length === 0 ? void 0 : deny2);
10823
+ const withDeny = setKey(permissions, "deny", lists.deny.length === 0 ? void 0 : lists.deny);
10824
+ const withAsk = setKey(withDeny, "ask", lists.ask.length === 0 ? void 0 : lists.ask);
10825
+ const next2 = setKey(withAsk, BLOCK_READS, lists.blockReads);
10823
10826
  return setKey(settings, "permissions", Object.keys(next2).length === 0 ? void 0 : next2);
10824
10827
  };
10828
+ var missing = (wanted, existing) => wanted.filter((rule) => !existing.includes(rule));
10829
+ var withoutAdded = (existing, added) => existing.filter((rule) => typeof rule !== "string" || !added.includes(rule));
10825
10830
  var ownHookMatcher = (recorded) => (handler) => isOwnHandler(handler) || recorded.some((own) => JSON.stringify(own) === JSON.stringify(handler));
10826
10831
  var mergeSettings = (current, wanted, recorded) => {
10827
10832
  const schema = Object.keys(current).length === 0;
10828
10833
  const base = schema ? { $schema: SETTINGS_SCHEMA_URL } : current;
10829
10834
  const hooks = mergeHooks(asRecord(base["hooks"]), ownHookMatcher(recorded), wanted.hooks);
10830
- const existingDeny = asList(asRecord(base["permissions"])["deny"]);
10831
- const deny2 = wanted.deny.filter((rule) => !existingDeny.includes(rule));
10835
+ const permissions = asRecord(base["permissions"]);
10836
+ const existingDeny = asList(permissions["deny"]);
10837
+ const existingAsk = asList(permissions["ask"]);
10838
+ const deny2 = missing(wanted.deny, existingDeny);
10839
+ const ask = missing(wanted.ask, existingAsk);
10840
+ const blockReads = !(BLOCK_READS in permissions);
10832
10841
  const attribution = wanted.attributionOff && !("attribution" in base);
10833
10842
  const marketplace = wanted.marketplace && !hasEntry(base, "extraKnownMarketplaces", MARKETPLACE_KEY);
10834
10843
  const plugin = wanted.marketplace && !hasEntry(base, "enabledPlugins", PLUGIN_ID);
10835
- let settings = withPermissions(withHooks(base, hooks), [...existingDeny, ...deny2]);
10844
+ let settings = withPermissions(withHooks(base, hooks), {
10845
+ deny: [...existingDeny, ...deny2],
10846
+ ask: [...existingAsk, ...ask],
10847
+ blockReads: blockReads ? true : permissions[BLOCK_READS]
10848
+ });
10836
10849
  if (attribution) settings = { ...settings, attribution: ATTRIBUTION_OFF };
10837
10850
  if (marketplace) settings = withEntry(settings, "extraKnownMarketplaces", MARKETPLACE_KEY, MARKETPLACE_ENTRY);
10838
10851
  if (plugin) settings = withEntry(settings, "enabledPlugins", PLUGIN_ID, true);
10839
- return { settings, added: { schema, deny: deny2, attribution, marketplace, plugin } };
10852
+ return { settings, added: { schema, deny: deny2, ask, blockReads, attribution, marketplace, plugin } };
10840
10853
  };
10841
10854
  var sameAttribution = (value) => JSON.stringify(value) === JSON.stringify(ATTRIBUTION_OFF);
10842
10855
  var unmergeSettings = (current, added, recorded) => {
10843
10856
  const hooks = mergeHooks(asRecord(current["hooks"]), ownHookMatcher(recorded), {});
10844
- const deny2 = asList(asRecord(current["permissions"])["deny"]).filter(
10845
- (rule) => typeof rule !== "string" || !added.deny.includes(rule)
10846
- );
10847
- let settings = withPermissions(withHooks(current, hooks), deny2);
10857
+ const permissions = asRecord(current["permissions"]);
10858
+ let settings = withPermissions(withHooks(current, hooks), {
10859
+ deny: withoutAdded(asList(permissions["deny"]), added.deny),
10860
+ ask: withoutAdded(asList(permissions["ask"]), added.ask),
10861
+ blockReads: added.blockReads && permissions[BLOCK_READS] === true ? void 0 : permissions[BLOCK_READS]
10862
+ });
10848
10863
  if (added.attribution && sameAttribution(settings["attribution"])) {
10849
10864
  settings = setKey(settings, "attribution", void 0);
10850
10865
  }
@@ -11050,9 +11065,11 @@ var parseAdded = (value) => {
11050
11065
  const deny2 = strings2(value["deny"]);
11051
11066
  const { schema, attribution } = value;
11052
11067
  if (deny2 === void 0 || typeof schema !== "boolean" || typeof attribution !== "boolean") return void 0;
11068
+ const ask = strings2(value["ask"]) ?? [];
11069
+ const blockReads = value["blockReads"] === true;
11053
11070
  const marketplace = value["marketplace"] === true;
11054
11071
  const plugin = value["plugin"] === true;
11055
- return { schema, attribution, deny: deny2, marketplace, plugin };
11072
+ return { schema, attribution, deny: deny2, ask, blockReads, marketplace, plugin };
11056
11073
  };
11057
11074
  var parseEntry2 = (value) => {
11058
11075
  if (!isRecord(value)) return void 0;
@@ -11099,6 +11116,42 @@ var parseLock = (text6) => {
11099
11116
  var renderLock = (lock) => `${JSON.stringify(lock, null, 2)}
11100
11117
  `;
11101
11118
 
11119
+ // src/install/ask-floor.ts
11120
+ var DEFAULT_BRANCHES = ["main", "master"];
11121
+ var pushTo = (branch) => [
11122
+ `Bash(git push * ${branch})`,
11123
+ `Bash(git push * ${branch} *)`,
11124
+ `Bash(git push *:${branch})`,
11125
+ `Bash(git push *:${branch} *)`
11126
+ ];
11127
+ var ASK_FLOOR = {
11128
+ push: DEFAULT_BRANCHES.flatMap(pushTo),
11129
+ deploy: [
11130
+ "Bash(npm publish *)",
11131
+ "Bash(pnpm publish *)",
11132
+ "Bash(yarn publish *)",
11133
+ "Bash(yarn npm publish *)",
11134
+ "Bash(bun publish *)",
11135
+ "Bash(vercel * --prod*)",
11136
+ "Bash(vercel --prod*)",
11137
+ "Bash(netlify deploy * --prod*)",
11138
+ "Bash(fly deploy *)",
11139
+ "Bash(flyctl deploy *)",
11140
+ "Bash(firebase deploy *)",
11141
+ "Bash(wrangler deploy *)",
11142
+ "Bash(serverless deploy *)",
11143
+ "Bash(sls deploy *)",
11144
+ "Bash(cdk deploy *)",
11145
+ "Bash(terraform apply *)",
11146
+ "Bash(tofu apply *)",
11147
+ "Bash(pulumi up *)",
11148
+ "Bash(kubectl apply *)",
11149
+ "Bash(helm install *)",
11150
+ "Bash(helm upgrade *)"
11151
+ ]
11152
+ };
11153
+ var ASK_RULES = [...ASK_FLOOR.push, ...ASK_FLOOR.deploy];
11154
+
11102
11155
  // src/install/claude-version.ts
11103
11156
  var ON_FAILURE_MIN = [2, 1, 295];
11104
11157
  var PROBE_MS = 8e3;
@@ -11120,6 +11173,23 @@ var detectClaude = (probe) => {
11120
11173
  return { version: parsed.join("."), failClosed: atLeast(parsed, ON_FAILURE_MIN) };
11121
11174
  };
11122
11175
 
11176
+ // src/install/claudeignore.ts
11177
+ var CLAUDEIGNORE_FILES = [".claudeignore", ".claude/.claudeignore"];
11178
+ var ruleFor = (raw) => {
11179
+ const line2 = withoutCarriageReturn(raw).trim();
11180
+ if (line2 === "" || line2.startsWith("#") || line2.startsWith("!") || line2.includes(")")) return void 0;
11181
+ const anchored = line2.startsWith("/");
11182
+ const body = (anchored ? line2.slice(1) : line2).replace(/\/+$/, "");
11183
+ if (body === "" || body === "*" || body === "**") return void 0;
11184
+ const path = line2.endsWith("/") ? `${body}/**` : body;
11185
+ return `Read(${anchored || body.includes("/") ? "./" : ""}${path})`;
11186
+ };
11187
+ var claudeignoreRules = (texts) => [
11188
+ ...new Set(
11189
+ texts.flatMap((text6) => (text6 ?? "").split("\n")).map(ruleFor).filter((rule) => rule !== void 0)
11190
+ )
11191
+ ];
11192
+
11123
11193
  // src/install/config-template.ts
11124
11194
  var configTemplate = (t, preset, lang) => [
11125
11195
  `# ${GENERATED_MARKER}`,
@@ -11346,6 +11416,8 @@ var joinAdded = (previous, next2) => previous === void 0 ? next2 : {
11346
11416
  schema: previous.schema || next2.schema,
11347
11417
  attribution: previous.attribution || next2.attribution,
11348
11418
  deny: union(previous.deny, next2.deny),
11419
+ ask: union(previous.ask, next2.ask),
11420
+ blockReads: previous.blockReads || next2.blockReads,
11349
11421
  marketplace: previous.marketplace || next2.marketplace,
11350
11422
  plugin: previous.plugin || next2.plugin
11351
11423
  };
@@ -11359,7 +11431,8 @@ var settingsItem = (project, detection, input, previous) => {
11359
11431
  const hooks = wantedHooks(detection);
11360
11432
  const wanted = {
11361
11433
  hooks,
11362
- deny: DENY_RULES,
11434
+ deny: [...DENY_RULES, ...claudeignoreRules(CLAUDEIGNORE_FILES.map((path) => project.read(path)))],
11435
+ ask: ASK_RULES,
11363
11436
  attributionOff: input.preset === "gabriel",
11364
11437
  marketplace: input.marketplace
11365
11438
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "finitemonkey",
3
- "version": "0.1.0-next.8",
3
+ "version": "0.1.0-next.9",
4
4
  "description": "Claude Code hooks that stop a coding agent from saying done before the tests pass: a machine-checked definition of done, plus guardrails for secrets, git safety and production. Pre-release.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",