@sleepy_panda_srl/portulan 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +15 -4
  2. package/cli/README.md +61 -6
  3. package/cli/advisory.mjs +167 -53
  4. package/cli/cache-risk.mjs +69 -0
  5. package/cli/comments.mjs +8 -5
  6. package/cli/compile.mjs +781 -130
  7. package/cli/context.mjs +204 -43
  8. package/cli/doctor.mjs +248 -31
  9. package/cli/drills.mjs +56 -9
  10. package/cli/finish.md +34 -0
  11. package/cli/finish.mjs +8 -67
  12. package/cli/form.mjs +34 -18
  13. package/cli/frontmatter.mjs +39 -0
  14. package/cli/gate.mjs +14 -12
  15. package/cli/guidance-files.mjs +335 -0
  16. package/cli/handoff-policy.mjs +76 -0
  17. package/cli/html-entities.mjs +2130 -0
  18. package/cli/import-files.mjs +15 -0
  19. package/cli/index.mjs +77 -24
  20. package/cli/init-preview.mjs +553 -0
  21. package/cli/init.mjs +261 -46
  22. package/cli/instructions-journal.mjs +1185 -0
  23. package/cli/instructions.mjs +332 -57
  24. package/cli/ledger.mjs +230 -40
  25. package/cli/librarian.mjs +103 -19
  26. package/cli/persona-routing.mjs +209 -0
  27. package/cli/plugin-lint.mjs +77 -56
  28. package/cli/portulan.mjs +1 -1
  29. package/cli/recipe-output.mjs +91 -0
  30. package/cli/recipe-runner.md +68 -0
  31. package/cli/recipe-runner.mjs +131 -0
  32. package/cli/recipe-set.mjs +12 -3
  33. package/cli/review-meter.mjs +14 -9
  34. package/cli/ritual-contexts.mjs +103 -0
  35. package/cli/sessions.mjs +2 -1
  36. package/cli/spend-analysis.mjs +111 -0
  37. package/cli/spend-offer.md +53 -0
  38. package/cli/spend-offer.mjs +93 -0
  39. package/cli/stop-diagnostics.mjs +114 -0
  40. package/cli/stop-gate.mjs +133 -90
  41. package/cli/stop-policy.mjs +14 -0
  42. package/cli/symbols.mjs +55 -8
  43. package/cli/telemetry.mjs +66 -17
  44. package/cli/upgrade.mjs +62 -44
  45. package/cli/vendor.mjs +207 -125
  46. package/core/operating/context.md +401 -18
  47. package/core/operating/evolution.md +3 -2
  48. package/core/operating/loop.md +9 -4
  49. package/core/operating/sessions.md +6 -15
  50. package/core/personas/README.md +75 -0
  51. package/core/personas/implementer.md +3 -0
  52. package/core/personas/librarian.md +3 -0
  53. package/core/personas/reviewer.md +3 -0
  54. package/core/skills/clarify/SKILL.md +1 -1
  55. package/core/skills/consolidate/SKILL.md +1 -1
  56. package/core/templates/handoff.md +6 -2
  57. package/evals/README.md +23 -7
  58. package/evals/ab/personas/README.md +111 -0
  59. package/evals/ab/personas/acquisition.json +330 -0
  60. package/evals/ab/personas/bindings/implementer.txt +43 -0
  61. package/evals/ab/personas/bindings/librarian.txt +40 -0
  62. package/evals/ab/personas/bindings/reviewer.txt +41 -0
  63. package/evals/ab/personas/corpus.json +69 -0
  64. package/evals/ab/personas/grader.mjs +171 -0
  65. package/evals/ab/personas/implementer.json +774 -0
  66. package/evals/ab/personas/implementer.md +22 -0
  67. package/evals/ab/personas/librarian.json +758 -0
  68. package/evals/ab/personas/librarian.md +22 -0
  69. package/evals/ab/personas/protocol/continuation.mjs.txt +284 -0
  70. package/evals/ab/personas/protocol/first-outcome.mjs.txt +227 -0
  71. package/evals/ab/personas/protocol/first-probe.mjs.txt +172 -0
  72. package/evals/ab/personas/protocol/publisher.mjs.txt +126 -0
  73. package/evals/ab/personas/protocol/second-probe.mjs.txt +193 -0
  74. package/evals/ab/personas/repairs/first-control-answer.txt +23 -0
  75. package/evals/ab/personas/repairs/grader-before-decode-repair.mjs.txt +143 -0
  76. package/evals/ab/personas/reviewer.json +758 -0
  77. package/evals/ab/personas/reviewer.md +22 -0
  78. package/evals/releases/0.3.0.json +150 -0
  79. package/evals/releases/0.3.0.md +68 -0
  80. package/evals/releases/0.4.0.json +154 -0
  81. package/evals/releases/0.4.0.md +69 -0
  82. package/package.json +7 -2
  83. package/packs/.claude-plugin/plugin.json +2 -1
  84. package/packs/README.md +7 -4
  85. package/packs/rituals/checkpoints/README.md +35 -0
  86. package/packs/rituals/checkpoints/pack.json +3 -2
  87. package/packs/tools/README.md +8 -8
  88. package/packs/tools/local-llm/README.md +176 -0
  89. package/packs/tools/local-llm/pack.json +32 -0
  90. package/packs/tools/local-llm/personas/local-worker.md +44 -0
  91. package/packs/tools/local-llm/runner/local-llm.mjs +1779 -0
  92. package/packs/tools/local-llm/skills/delegate-local/SKILL.md +88 -0
  93. package/packs/tools/local-llm/verify/README.md +18 -0
  94. package/spec/README.md +37 -3
  95. package/spec/migrations/0009-instruction-sections-on-read.mjs +60 -11
  96. package/spec/migrations/0011-persona-bindings-compiled.mjs +23 -0
  97. package/spec/migrations/README.md +6 -0
  98. package/spec/pack.schema.json +12 -3
  99. package/spec/slots.md +272 -123
  100. package/spec/workspace.schema.json +67 -11
@@ -0,0 +1,69 @@
1
+ // Potential cache risks from project files; host activation is not inferred.
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { readOwnedText } from "./compile.mjs";
5
+
6
+ const object = v => v !== null && typeof v === "object" && !Array.isArray(v);
7
+ const strings = v => Array.isArray(v) && v.every(s => typeof s === "string");
8
+ const scope = "Project files only; full MCP schema, trust, live activation, user/managed/CLI overrides, broader model/MCP policies and untested selectors (including experimental-beta disabling) are unassessed.";
9
+
10
+ export function cacheRiskLines(root) {
11
+ const lines = [`Assessed selectors: project model/ANTHROPIC_MODEL (opusplan and conditional Haiku), ENABLE_TOOL_SEARCH=false and ToolSearch denial; ${scope}`];
12
+ const read = label => {
13
+ const file = path.join(root, label);
14
+ try { fs.lstatSync(file); }
15
+ catch (error) {
16
+ if (error.code === "ENOENT") return {};
17
+ lines.push(`${label}: unassessed — project source is unreadable.`); return {};
18
+ }
19
+ try {
20
+ const value = JSON.parse(readOwnedText(file, root));
21
+ if (!object(value)) throw Error();
22
+ return value;
23
+ } catch { lines.push(`${label}: unassessed — project source is unreadable, malformed, nonregular or outside its tree.`); return {}; }
24
+ };
25
+ const settings = label => {
26
+ const value = read(label);
27
+ if ((value.model !== undefined && typeof value.model !== "string") ||
28
+ (value.env !== undefined && (!object(value.env) || ["ANTHROPIC_MODEL", "ENABLE_TOOL_SEARCH"].some(k => value.env[k] !== undefined && typeof value.env[k] !== "string"))) ||
29
+ (value.permissions !== undefined && (!object(value.permissions) || (value.permissions.deny !== undefined && !strings(value.permissions.deny)))) ||
30
+ ["enabledMcpjsonServers", "disabledMcpjsonServers"].some(k => value[k] !== undefined && !strings(value[k])) ||
31
+ (value.enableAllProjectMcpServers !== undefined && typeof value.enableAllProjectMcpServers !== "boolean")) {
32
+ lines.push(`${label}: unassessed — relevant project settings have invalid shapes.`); return {};
33
+ }
34
+ return value;
35
+ };
36
+ const shared = settings(".claude/settings.json"), local = settings(".claude/settings.local.json");
37
+ const env = { ...shared.env, ...local.env };
38
+ const model = env.ANTHROPIC_MODEL || (local.model ?? shared.model);
39
+ const source = env.ANTHROPIC_MODEL ? "project ANTHROPIC_MODEL" : "project model";
40
+ if (typeof model === "string" && /^opusplan(?:\[1m\])?$/i.test(model)) {
41
+ lines.push(`plan-mode model switching configured via ${source}: Opus planning and Sonnet execution can rebuild the model-specific cache at the boundary.`);
42
+ } else if (typeof model === "string" && /^(?:haiku(?:\[1m\])?|claude-haiku-[a-z0-9.-]+)$/i.test(model)) {
43
+ lines.push(`plan-mode model upgrade possible via ${source} for a Haiku selection: a permitted Sonnet upgrade can rebuild the model-specific cache. Provider and allowlist behavior is unresolved.`);
44
+ } else if (model === "") lines.push("project model selection is empty: resolved default and plan-mode behavior are unassessed.");
45
+ const mcp = read(".mcp.json");
46
+ const candidates = [];
47
+ if (mcp.mcpServers !== undefined && !object(mcp.mcpServers)) lines.push(".mcp.json: unassessed — project MCP definitions have an invalid shape.");
48
+ else for (const [name, server] of Object.entries(mcp.mcpServers ?? {})) {
49
+ const stdio = object(server) && (server.type === undefined || server.type === "stdio") && typeof server.command === "string" && server.command.trim() !== "";
50
+ let remote = false;
51
+ if (object(server) && ["http", "sse", "ws"].includes(server.type) && typeof server.url === "string") {
52
+ try { remote = (server.type === "ws" ? ["ws:", "wss:"] : ["http:", "https:"]).includes(new URL(server.url).protocol); } catch { /* Invalid endpoint is never printed. */ }
53
+ }
54
+ if (!/^[a-zA-Z0-9_-]+$/.test(name) || (!stdio && !remote) || server.disabled !== undefined ||
55
+ (server.args !== undefined && !strings(server.args)) || (server.env !== undefined && (!object(server.env) || !Object.values(server.env).every(v => typeof v === "string")))) {
56
+ lines.push(".mcp.json: unassessed — a project MCP definition is unsupported or invalid.");
57
+ } else candidates.push(name);
58
+ }
59
+ const rejected = new Set([...(shared.disabledMcpjsonServers ?? []), ...(local.disabledMcpjsonServers ?? [])]);
60
+ const approved = new Set([...(shared.enabledMcpjsonServers ?? []), ...(local.enabledMcpjsonServers ?? [])]);
61
+ const all = local.enableAllProjectMcpServers ?? shared.enableAllProjectMcpServers ?? false;
62
+ const eligible = candidates.filter(name => !rejected.has(name));
63
+ const denial = [...(shared.permissions?.deny ?? []), ...(local.permissions?.deny ?? [])].includes("ToolSearch");
64
+ if (eligible.length && (env.ENABLE_TOOL_SEARCH === "false" || denial)) {
65
+ const known = eligible.filter(name => all || approved.has(name)).length;
66
+ lines.push(`tool search disabled by ${denial ? "project ToolSearch denial" : "project ENABLE_TOOL_SEARCH=false"}: ${eligible.length} eligible project MCP candidate(s), ${known} with project-file approval; upfront tool definitions can enlarge or change the cache prefix.`);
67
+ }
68
+ return lines;
69
+ }
package/cli/comments.mjs CHANGED
@@ -764,12 +764,15 @@ export function historyOf(comments) {
764
764
  return comments.filter((comment) => kinds.has(comment)).map((comment) => ({ ...comment, kinds: [...kinds.get(comment)] }));
765
765
  }
766
766
 
767
- function trackedFiles(root) {
767
+ function trackedFiles(root, timeout, env, noFsmonitor) {
768
768
  try {
769
- const out = execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], {
769
+ const out = execFileSync("git", [...(noFsmonitor ? ["-c", "core.fsmonitor=false"] : []), "ls-files", "--cached", "--others", "--exclude-standard", "-z"], {
770
770
  cwd: root,
771
771
  encoding: "utf8",
772
772
  maxBuffer: 256 * 1024 * 1024,
773
+ timeout,
774
+ env,
775
+ killSignal: "SIGKILL",
773
776
  stdio: ["ignore", "pipe", "pipe"],
774
777
  });
775
778
  return [...new Set(out.split("\0").filter(Boolean))].sort();
@@ -799,8 +802,8 @@ function readSource(root, file, linked) {
799
802
  }
800
803
 
801
804
  /** Every file the tree holds in a language this reads, with its comments and the ones recording history. */
802
- export function scan(root, { exclude = [] } = {}) {
803
- const files = trackedFiles(root);
805
+ export function scan(root, { exclude = [], timeout, env, noFsmonitor = false } = {}) {
806
+ const files = trackedFiles(root, timeout, env, noFsmonitor);
804
807
  const unused = exclude.filter((prefix) => !files.some((file) => file.startsWith(prefix)));
805
808
  if (unused.length) throw new CommentsError(`--exclude ${unused.join(", ")} names no file in the tree: remove it`);
806
809
  const read = [];
@@ -822,7 +825,7 @@ export function scan(root, { exclude = [] } = {}) {
822
825
  const sum = (items, pick) => items.reduce((total, item) => total + pick(item), 0);
823
826
  const figure = (n) => n.toLocaleString("en-US");
824
827
 
825
- export const historyCount = (root) => sum(scan(root), (entry) => entry.history.length);
828
+ export const historyCount = (root, options) => sum(scan(root, options), (entry) => entry.history.length);
826
829
 
827
830
  function directoryTable(read) {
828
831
  const directories = new Map();