agentplugged 1.0.2 → 1.0.4

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.
@@ -14500,6 +14500,10 @@ retrospective, an installation and a validation point never wait for it.
14500
14500
  scope: \`guard.creator_action\` \`scope_widen\`) or a decision of the architecture contradicted, and
14501
14501
  lets the write through in doubt. Reading, and writes in other tools, stay governed by the
14502
14502
  permission rules alone: \`read_secrets\` is always denied, whatever the session.
14503
+ 5. A write the engine refused is never redone by another path: not by a terminal command
14504
+ (\`sed -i\`, a redirection \`>\`, \`Set-Content\`, \`Out-File\`, a copy, a script), not by another tool.
14505
+ The agent stops, tells the coordinator which file it needs and why, and the coordinator offers
14506
+ the creator to widen the scope (\`guard.creator_action\` \`scope_widen\`) in one gesture.
14503
14507
  `;
14504
14508
  }
14505
14509
  /** The folder layout and the seven rules: in the short constitution and in the reference alike. */
@@ -17783,6 +17787,21 @@ function claudeActionRules(action, mainBranch) {
17783
17787
  push(rules, patternsOf(action, mainBranch).flatMap(claudeRules));
17784
17788
  return rules;
17785
17789
  }
17790
+ /**
17791
+ * The rules as Claude Code's settings hold them. A backslash right before the closing parenthesis
17792
+ * escapes it there, so such a rule is written with that one backslash doubled (the others are
17793
+ * read literally). Claude Code's `PowerShell` tool has its own rules: each `Bash(x)` rule gets
17794
+ * its `PowerShell(x)` twin, or a command would pass by being run through the other shell.
17795
+ */
17796
+ function claudeCommandRules(rules) {
17797
+ const out = [];
17798
+ for (const rule of rules) {
17799
+ const escaped = rule.endsWith("\\)") && !rule.endsWith("\\\\)") ? `${rule.slice(0, -1)}\\)` : rule;
17800
+ push(out, [escaped]);
17801
+ if (escaped.startsWith("Bash(")) push(out, [`PowerShell${escaped.slice(4)}`]);
17802
+ }
17803
+ return out;
17804
+ }
17786
17805
  /** The Claude Code rule that allows all the tools of the `agentplugged` MCP server. */
17787
17806
  const ENGINE_MCP_RULE = "mcp__agentplugged";
17788
17807
  /**
@@ -18991,13 +19010,22 @@ const WRITE_TOOLS = [
18991
19010
  text: "new_source"
18992
19011
  }
18993
19012
  ];
19013
+ /** The two tools that run a shell command in Claude Code: both carry it in `tool_input.command`. */
19014
+ const SHELL_MATCHER = "Bash|PowerShell";
19015
+ /**
19016
+ * The `if` conditions of a hook on the shell tools: each `Bash(x)` condition and its
19017
+ * `PowerShell(x)` twin, as a condition matches the calls of one tool only.
19018
+ */
19019
+ function shellConditions(conditions) {
19020
+ return conditions.flatMap((condition) => [condition, `PowerShell${condition.slice(4)}`]);
19021
+ }
18994
19022
  /** The hooks `sync` writes for Claude Code, from the rules in force. */
18995
19023
  function claudeTrustHooks(rules, mainBranch) {
18996
19024
  const session = hookValue("session_id");
18997
19025
  const pre = [];
18998
- const conditions = rules.install_dependency === "deny" ? [] : claudeActionRules("install_dependency", mainBranch).filter((entry) => entry.startsWith("Bash("));
19026
+ const conditions = rules.install_dependency === "deny" ? [] : shellConditions(claudeActionRules("install_dependency", mainBranch).filter((entry) => entry.startsWith("Bash(")));
18999
19027
  if (conditions.length > 0) pre.push({
19000
- matcher: "Bash",
19028
+ matcher: SHELL_MATCHER,
19001
19029
  hooks: conditions.map((condition) => handler("check_action", {
19002
19030
  action: "install_dependency",
19003
19031
  command: hookValue("tool_input.command"),
@@ -19032,9 +19060,9 @@ function claudeTrustHooks(rules, mainBranch) {
19032
19060
  })]
19033
19061
  },
19034
19062
  {
19035
- matcher: "Bash",
19036
- hooks: NETWORK_COMMANDS.map((condition) => handler("record_external_read", {
19037
- tool: "Bash",
19063
+ matcher: SHELL_MATCHER,
19064
+ hooks: shellConditions(NETWORK_COMMANDS).map((condition) => handler("record_external_read", {
19065
+ tool: condition.startsWith("PowerShell(") ? "PowerShell" : "Bash",
19038
19066
  source: hookValue("tool_input.command"),
19039
19067
  session
19040
19068
  }, condition))
@@ -19775,9 +19803,9 @@ const syncCommand = defineCommand({
19775
19803
  flavor: claudeFlavor(loaded.config.permissions?.mode),
19776
19804
  current: claudeSettings
19777
19805
  }, has("claude-code") ? {
19778
- deny: claudeWritable(unique([...denyRules(permissionInputs), ...claudeRules.deny])),
19779
- allow: claudeWritable(claudeRules.allow),
19780
- ask: claudeWritable(claudeRules.ask)
19806
+ deny: claudeCommandRules(unique([...denyRules(permissionInputs), ...claudeRules.deny])),
19807
+ allow: claudeCommandRules(claudeRules.allow),
19808
+ ask: claudeCommandRules(claudeRules.ask)
19781
19809
  } : {}, {
19782
19810
  deny: manifest.permissions,
19783
19811
  ...previousLists[CLAUDE_SETTINGS_PATH]
@@ -19809,7 +19837,7 @@ const syncCommand = defineCommand({
19809
19837
  count: owned.allow.length
19810
19838
  });
19811
19839
  }
19812
- const conflicts = [...has("claude-code") ? allowConflicts(CLAUDE_SETTINGS_PATH, claudeSettings?.value, claudeRules.allow, {
19840
+ const conflicts = [...has("claude-code") ? allowConflicts(CLAUDE_SETTINGS_PATH, claudeSettings?.value, claudeCommandRules(claudeRules.allow), {
19813
19841
  deny: manifest.permissions,
19814
19842
  ...previousLists[CLAUDE_SETTINGS_PATH]
19815
19843
  }) : [], ...has("cursor") ? allowConflicts(CURSOR_CLI_PATH, cursorSettings?.value, cursorRules.allow, { deny: previousCursor }) : []];
@@ -19878,14 +19906,6 @@ const CODEX_HOME_DENY = [
19878
19906
  function unique(rules) {
19879
19907
  return [...new Set(rules)];
19880
19908
  }
19881
- /**
19882
- * The rules Claude Code's settings can hold: a backslash before the closing parenthesis escapes it
19883
- * there, so such a rule would be malformed (the `C:/` spelling of the same root stays). The
19884
- * engine's own guards keep them: they read the rules themselves.
19885
- */
19886
- function claudeWritable(rules) {
19887
- return rules.filter((rule) => !rule.endsWith("\\)"));
19888
- }
19889
19909
  /** The tool a generated path belongs to, for the summary. */
19890
19910
  function groupOf(path) {
19891
19911
  if (path.startsWith(".claude/") || path === "CLAUDE.md") return "claude-code";
@@ -20015,6 +20035,17 @@ function compareSemver(a, b) {
20015
20035
  }
20016
20036
  return 0;
20017
20037
  }
20038
+ /** The engine version `method.yaml` pins in the repository's main working folder, if readable. */
20039
+ async function mainCheckoutPin(files) {
20040
+ try {
20041
+ const text = await files.readMainCheckoutText?.(METHOD_CONFIG_PATH);
20042
+ if (text === void 0) return void 0;
20043
+ const version = parseDocument(text).toJS()?.engine_version;
20044
+ return SemverSchema.safeParse(version).success ? version : void 0;
20045
+ } catch {
20046
+ return;
20047
+ }
20048
+ }
20018
20049
  /**
20019
20050
  * Checks, before anything is written, that the running engine may work on this product:
20020
20051
  * a `format_version` newer than the engine knows stops with `UPGRADE_REQUIRED`, an older one with
@@ -20057,6 +20088,11 @@ async function checkEngine(files, options) {
20057
20088
  overridden: false,
20058
20089
  older: false
20059
20090
  };
20091
+ if (!options.upgrade && !options.engineAny && compareSemver(options.engineVersion, pinned) > 0 && await mainCheckoutPin(files) === options.engineVersion) return {
20092
+ pinned,
20093
+ overridden: false,
20094
+ older: false
20095
+ };
20060
20096
  const details = {
20061
20097
  path: METHOD_CONFIG_PATH,
20062
20098
  pinned,
@@ -31012,7 +31048,7 @@ function readSource(tool, raw) {
31012
31048
  const toolName = truncate(cleanText(tool), 200) || "unknown";
31013
31049
  const given = cleanText(raw ?? "");
31014
31050
  const usable = given !== "" && !given.includes("${");
31015
- if (usable && toolName === "Bash") return {
31051
+ if (usable && (toolName === "Bash" || toolName === "PowerShell")) return {
31016
31052
  tool: toolName,
31017
31053
  source: truncate(hostOf$1(given, true) ?? commandName(given), 200)
31018
31054
  };
package/dist/main.js CHANGED
@@ -6299,6 +6299,28 @@ var NodeWorkspaceFiles = class {
6299
6299
  async readBytes(path) {
6300
6300
  return new Uint8Array(await readFile(this.resolve(path)));
6301
6301
  }
6302
+ async readMainCheckoutText(path) {
6303
+ try {
6304
+ const target = this.resolve(path);
6305
+ for (let top = this.root;; top = dirname(top)) {
6306
+ const marker = join(top, ".git");
6307
+ const info = await stat(marker).catch(() => void 0);
6308
+ if (info?.isDirectory() === true) return void 0;
6309
+ if (info?.isFile() === true) {
6310
+ const named = /^gitdir:\s*(.+?)\s*$/m.exec(await readFile(marker, "utf8"))?.[1];
6311
+ if (named === void 0) return void 0;
6312
+ const own = resolve(top, named);
6313
+ const common = resolve(own, (await readFile(join(own, "commondir"), "utf8")).trim());
6314
+ if (basename(common) !== ".git") return void 0;
6315
+ const main = join(dirname(common), relative(top, this.root));
6316
+ return await readFile(join(main, relative(this.root, target)), "utf8");
6317
+ }
6318
+ if (dirname(top) === top) return void 0;
6319
+ }
6320
+ } catch {
6321
+ return;
6322
+ }
6323
+ }
6302
6324
  /**
6303
6325
  * Writes to a temporary file next to the target, then renames it over the target. A symbolic
6304
6326
  * link is followed, so the real file is replaced and the creator's link is kept.
@@ -59,6 +59,7 @@ back, and the decisions to take.
59
59
  other tool under his id (`ROLE_FORBIDDEN`): he never opens a checkpoint (an end of phase
60
60
  included), never starts or delivers a story, never submits a result in another agent's place.
61
61
  He never reports under another agent's id.
62
+ - When a developer reports a write the engine refused, Noah never lets it be redone by another path (a terminal command, a redirection, a copy): he proposes to the creator to widen the scope (`guard.creator_action` `scope_widen`), in one gesture.
62
63
  - Decisions belong to the creator: the scope, the options of a checkpoint, an approval, a new
63
64
  agent, and every point they kept for themselves. Noah recommends; the creator decides. The points
64
65
  they did not keep are decided by the agent responsible, never by Noah (`checkpoint.decide` is not
@@ -16803,7 +16803,8 @@ function alreadyRecorded(listing, read, session) {
16803
16803
  * source is a `${…}`), or for an absent source: when in doubt, the read counts.
16804
16804
  */
16805
16805
  function isLocalShellCommand(tool, source) {
16806
- if (tool.trim() !== "Bash" || source === void 0) return false;
16806
+ const shell = tool.trim();
16807
+ if (shell !== "Bash" && shell !== "PowerShell" || source === void 0) return false;
16807
16808
  const command = source.trim();
16808
16809
  if (command === "" || /^\$\{[^}]*\}$/.test(command) || /^https?:\/\//i.test(command)) return false;
16809
16810
  return !isNetworkCommand(command);
@@ -17101,9 +17102,14 @@ const FILE_EXTENSIONS = "ts|tsx|js|jsx|mjs|cjs|json|jsonc|md|mdx|yaml|yml|toml|p
17101
17102
  const CODE_SPAN = /`([^`\n]{1,200})`/g;
17102
17103
  const BARE_PATH = new RegExp(`(?<![\\w@./:~-])((?:\\./)?[\\w@.-]+(?:/[\\w@.*-]+)*\\.(?:${FILE_EXTENSIONS}))(?![\\w/-])`, "g");
17103
17104
  const BARE_FOLDER = /(?<![\w@./:~-])((?:\.\/)?[\w@.-]+(?:\/[\w@.*-]+)+\/)(?![\w])/g;
17105
+ const TEMPLATE_SEGMENT = /^(?:<[A-Za-z_][\w-]*>|\{[A-Za-z_][\w-]*\}|\[[A-Za-z_][\w-]*\]|:[A-Za-z_][\w-]*)$/;
17104
17106
  /** A token read as a path of the project, or undefined (a URL, an absolute path, a word). */
17105
17107
  function pathOf$1(token) {
17106
17108
  let text = token.trim().replaceAll("\\", "/").replace(/^\{project-root\}\//, "").replace(/^\.\//, "");
17109
+ if (text.includes("/")) {
17110
+ if (TEMPLATE_SEGMENT.test(text.split("/")[0] ?? "")) return void 0;
17111
+ text = text.split("/").map((part) => TEMPLATE_SEGMENT.test(part) ? "*" : part).join("/");
17112
+ }
17107
17113
  text = text.replace(/[:#][\w,-]*$/, "");
17108
17114
  if (text === "" || text.length > 200) return void 0;
17109
17115
  if (/^(?:[a-z][a-z0-9+.-]*:|\/|~|[A-Za-z]:)/.test(text) || text.includes("//")) return void 0;
@@ -18353,7 +18359,7 @@ async function judge(context, index, request, root) {
18353
18359
  kind: "refuse",
18354
18360
  rule: "scope",
18355
18361
  story: owner.id,
18356
- message: `AgentPlugged refused this write: rule scope, story ${owner.id} does not name ${oneLine(relative, 120)}. If it is needed, ask the creator to widen the scope (guard.creator_action scope_widen {story: "${owner.id}", paths: ["${oneLine(relative, 120)}"]}); otherwise leave that file alone. Nothing was written.`
18362
+ message: `AgentPlugged refused this write: rule scope, story ${owner.id} does not name ${oneLine(relative, 120)}. If it is needed, ask the creator to widen the scope (guard.creator_action scope_widen {story: "${owner.id}", paths: ["${oneLine(relative, 120)}"]}); otherwise leave that file alone: do not write it by another tool or command (sed -i, a redirection, Set-Content, a copy, a script), and tell the coordinator which file you need and why. Nothing was written.`
18357
18363
  };
18358
18364
  if (request.text !== void 0 && index.decisions !== void 0) {
18359
18365
  const broken = decisionViolation(index.decisions.guards, relative, request.text);
@@ -18363,7 +18369,7 @@ async function judge(context, index, request, root) {
18363
18369
  story: owner.id,
18364
18370
  decision: broken.decision,
18365
18371
  detail: broken.detail,
18366
- message: `AgentPlugged refused this write: decision ${broken.decision} of the architecture forbids it (${oneLine(broken.detail, 120)}). Follow the decision, or ask the creator to change it first. Nothing was written.`
18372
+ message: `AgentPlugged refused this write: decision ${broken.decision} of the architecture forbids it (${oneLine(broken.detail, 120)}). Follow the decision, or ask the creator to change it first; do not write the file by another tool or command. Nothing was written.`
18367
18373
  };
18368
18374
  }
18369
18375
  if (placed === void 0) return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentplugged",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Install the AgentPlugged method in a repository and make its agents callable from Claude Code, Codex, Antigravity/Gemini CLI and Cursor.",
5
5
  "releaseSummary": "The engine updates itself: your session moves to a newer version between two calls, with nothing to reconnect.",
6
6
  "license": "FSL-1.1-MIT",
@@ -47,14 +47,14 @@
47
47
  "koffi": "~3.3.2"
48
48
  },
49
49
  "devDependencies": {
50
- "@agentplugged/adapters-git": "1.0.2",
51
- "@agentplugged/adapters-tools": "1.0.2",
52
- "@agentplugged/core": "1.0.2",
53
- "@agentplugged/crypto": "1.0.2",
54
- "@agentplugged/i18n": "1.0.2",
55
- "@agentplugged/mcp": "1.0.2",
50
+ "@agentplugged/adapters-git": "1.0.4",
51
+ "@agentplugged/adapters-tools": "1.0.4",
52
+ "@agentplugged/core": "1.0.4",
53
+ "@agentplugged/crypto": "1.0.4",
54
+ "@agentplugged/i18n": "1.0.4",
55
+ "@agentplugged/mcp": "1.0.4",
56
56
  "@agentplugged/method": "0.0.0",
57
- "@agentplugged/protocol": "1.0.2",
57
+ "@agentplugged/protocol": "1.0.4",
58
58
  "@biomejs/biome": "~2.5.15",
59
59
  "@modelcontextprotocol/client": "~2.3.0",
60
60
  "@types/node": "^24.19.1",