@primitive.ai/prim 0.1.0-alpha.56 → 0.1.0-alpha.58

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/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  checkAffectedDecisions,
13
13
  daemonOrDirectGet,
14
14
  formatDecisionsWarning
15
- } from "./chunk-VDLAAUWV.js";
15
+ } from "./chunk-YITJ2H3B.js";
16
16
  import {
17
17
  JOURNAL_DIR,
18
18
  SESSIONS_DIR,
@@ -21,14 +21,14 @@ import {
21
21
  listFlushing,
22
22
  pendingJournalStats,
23
23
  readMovesFromPath
24
- } from "./chunk-BOTKPLTI.js";
24
+ } from "./chunk-S4LFRVGD.js";
25
25
  import {
26
26
  printJson,
27
27
  registerSkillCommands
28
- } from "./chunk-ROL7SDXX.js";
28
+ } from "./chunk-UBEBEBLM.js";
29
29
  import {
30
30
  fetchFeedbackCapability
31
- } from "./chunk-WELBNODJ.js";
31
+ } from "./chunk-R4CUX6FY.js";
32
32
  import {
33
33
  inspectWorkspaceId
34
34
  } from "./chunk-IMAIBPUC.js";
@@ -36,16 +36,21 @@ import {
36
36
  binFile,
37
37
  commandMatchesBin,
38
38
  detachedHookShimCommand,
39
- hookShimCommand,
39
+ packageVersion,
40
+ pinnedHookCommand,
40
41
  warmBinCache
41
- } from "./chunk-HSZPTVKU.js";
42
+ } from "./chunk-YIMUIPMD.js";
42
43
  import {
43
- activateRepoBestEffort,
44
+ canonicalGitRoot,
45
+ canonicalRepositoryPath,
44
46
  decisionIngestionStatus,
45
47
  gitToplevel,
48
+ githubRepositoryFullName,
46
49
  isRepoActiveForCapture,
47
- setRepoActive
48
- } from "./chunk-R5ZJRSLV.js";
50
+ repoSyncId,
51
+ setRepoActive,
52
+ setRepoSyncId
53
+ } from "./chunk-LDQZASIF.js";
49
54
  import {
50
55
  HttpError,
51
56
  REFRESH_TOKEN_PATH,
@@ -60,7 +65,7 @@ import {
60
65
  resolveAuthCredential,
61
66
  setStoredToken,
62
67
  withFileLock
63
- } from "./chunk-DWDEQRWN.js";
68
+ } from "./chunk-I3HIPAIR.js";
64
69
  import {
65
70
  daemonIsLive,
66
71
  daemonRequest
@@ -74,7 +79,7 @@ import { Command } from "commander";
74
79
  import updateNotifier from "update-notifier";
75
80
 
76
81
  // src/commands/activation.ts
77
- function applyActivation(active) {
82
+ async function applyActivation(active) {
78
83
  const root = gitToplevel();
79
84
  if (!root) {
80
85
  process.stderr.write(
@@ -84,16 +89,33 @@ function applyActivation(active) {
84
89
  process.exit(1);
85
90
  }
86
91
  try {
87
- setRepoActive(process.cwd(), active);
92
+ let bound;
93
+ if (active) {
94
+ const repositoryFullName = githubRepositoryFullName(root);
95
+ if (!repositoryFullName) {
96
+ throw new Error("origin must be a GitHub HTTPS/SSH remote in owner/name form");
97
+ }
98
+ const response = await getClient().post("/api/cli/repositories/bind", {
99
+ repositoryFullName
100
+ });
101
+ if (typeof response.repoSyncId !== "string" || response.repoSyncId.length === 0) {
102
+ throw new Error("server returned no repository binding");
103
+ }
104
+ setRepoSyncId(root, response.repoSyncId);
105
+ bound = { repoSyncId: response.repoSyncId, repositoryFullName };
106
+ }
107
+ setRepoActive(root, active);
108
+ process.stderr.write(`[prim] prim ${active ? "enabled" : "disabled"} in ${root}
109
+ `);
110
+ printJson({ active, repo: root, ...bound });
88
111
  } catch (err) {
89
112
  const detail = err instanceof Error ? err.message : String(err);
90
- process.stderr.write(`[prim] failed to set prim.active: ${detail}
91
- `);
113
+ process.stderr.write(
114
+ `[prim] failed to ${active ? "bind and enable" : "disable"} prim: ${detail}
115
+ `
116
+ );
92
117
  process.exit(1);
93
118
  }
94
- process.stderr.write(`[prim] prim ${active ? "enabled" : "disabled"} in ${root}
95
- `);
96
- printJson({ active, repo: root });
97
119
  }
98
120
  function registerActivationCommands(program2) {
99
121
  program2.command("enable").description("Activate prim's hooks in this repo (git config prim.active=true)").action(() => applyActivation(true));
@@ -1068,7 +1090,7 @@ var SESSION_START_BIN = "prim-session-start";
1068
1090
  var SESSION_END_BIN = "prim-session-end";
1069
1091
  var STATUSLINE_BIN = "prim";
1070
1092
  var STATUSLINE_ARGS = "statusline";
1071
- var STATUSLINE_COMMAND = hookShimCommand(STATUSLINE_BIN, STATUSLINE_ARGS);
1093
+ var STATUSLINE_COMMAND = pinnedHookCommand(STATUSLINE_BIN, STATUSLINE_ARGS);
1072
1094
  var STATUSLINE_REFRESH_SECONDS = 5;
1073
1095
  var PRIM_BINS = [
1074
1096
  CAPTURE_BIN,
@@ -1092,8 +1114,13 @@ var CAPTURE_EVENTS = [
1092
1114
  "SessionEnd",
1093
1115
  "SubagentStop"
1094
1116
  ];
1095
- function makeRegistration(event, matcher, bin, args = "", opts = {}) {
1096
- return { event, matcher, bin, command: hookShimCommand(bin, args, opts) };
1117
+ function makeRegistration(event, matcher, bin, args = "") {
1118
+ return {
1119
+ event,
1120
+ matcher,
1121
+ bin,
1122
+ command: pinnedHookCommand(bin, args)
1123
+ };
1097
1124
  }
1098
1125
  function makeDetachedRegistration(event, matcher, bin, args = "") {
1099
1126
  return { event, matcher, bin, command: detachedHookShimCommand(bin, args) };
@@ -1102,11 +1129,9 @@ var REGISTRATIONS = [
1102
1129
  ...CAPTURE_EVENTS.map(
1103
1130
  (event) => event === "SessionEnd" ? makeDetachedRegistration(event, "*", CAPTURE_BIN) : makeRegistration(event, "*", CAPTURE_BIN)
1104
1131
  ),
1105
- makeRegistration("PreToolUse", "Edit|Write|MultiEdit", GATE_BIN),
1106
- makeRegistration("PostToolUse", "Edit|Write|MultiEdit", POST_TOOL_USE_BIN),
1107
- // Bare ladder (no branch-0): SessionStart must re-resolve @latest each session
1108
- // to refresh the bin cache the other hooks read. See lib/bin-cache.ts.
1109
- makeRegistration("SessionStart", "*", SESSION_START_BIN, "", { cacheRead: false }),
1132
+ makeRegistration("PreToolUse", "Edit|Write|MultiEdit|NotebookEdit|Bash", GATE_BIN),
1133
+ makeRegistration("PostToolUse", "Edit|Write|MultiEdit|NotebookEdit|Bash", POST_TOOL_USE_BIN),
1134
+ makeRegistration("SessionStart", "*", SESSION_START_BIN),
1110
1135
  makeDetachedRegistration("SessionEnd", "*", SESSION_END_BIN)
1111
1136
  ];
1112
1137
  var PRIM_PERMISSION_RULE = "Bash(npx --yes @primitive.ai/prim*)";
@@ -1347,9 +1372,6 @@ function performInstall(scope, force) {
1347
1372
  if (changed) {
1348
1373
  atomicWrite2(path, after);
1349
1374
  }
1350
- if (scope === "project") {
1351
- activateRepoBestEffort(process.cwd());
1352
- }
1353
1375
  return {
1354
1376
  scope,
1355
1377
  path,
@@ -1507,9 +1529,7 @@ var CODEX_REGISTRATIONS = [
1507
1529
  ...CODEX_CAPTURE_EVENTS.map((event) => makeRegistration(event, "*", CAPTURE_BIN2, CODEX_ARGS)),
1508
1530
  makeRegistration("PreToolUse", "apply_patch", GATE_BIN2, CODEX_ARGS),
1509
1531
  makeRegistration("PostToolUse", "apply_patch", POST_TOOL_USE_BIN2, CODEX_ARGS),
1510
- // Bare ladder (no branch-0): SessionStart re-resolves @latest each session to
1511
- // refresh the bin cache the other hooks read. See lib/bin-cache.ts.
1512
- makeRegistration("SessionStart", "*", SESSION_START_BIN2, CODEX_ARGS, { cacheRead: false })
1532
+ makeRegistration("SessionStart", "*", SESSION_START_BIN2, CODEX_ARGS)
1513
1533
  ];
1514
1534
  var USER_SCOPE_PATH2 = join3(homedir3(), ".codex", "hooks.json");
1515
1535
  var projectScopePath2 = () => join3(projectRoot(), ".codex", "hooks.json");
@@ -1562,9 +1582,6 @@ function performInstall2(scope, force) {
1562
1582
  if (changed) {
1563
1583
  atomicWrite2(path, after);
1564
1584
  }
1565
- if (scope === "project") {
1566
- activateRepoBestEffort(process.cwd());
1567
- }
1568
1585
  return resultFor(scope, path, after, changed);
1569
1586
  }
1570
1587
  function performUninstall2(scope) {
@@ -2775,7 +2792,9 @@ function toRequestBody(request) {
2775
2792
  alternatives: request.alternatives,
2776
2793
  confidence: request.confidence,
2777
2794
  reversibility: request.reversibility,
2778
- files: request.files
2795
+ files: request.files,
2796
+ protocolVersion: request.protocolVersion,
2797
+ repoSyncId: request.repoSyncId
2779
2798
  };
2780
2799
  const body = {};
2781
2800
  for (const [key, value] of Object.entries(candidate)) {
@@ -3035,14 +3054,16 @@ var CREATE_INACTIVE_PROMPT = "[prim] Decision ingestion is disabled here. Create
3035
3054
  var CREATE_INACTIVE_APPROVED = "[prim] one-time Decision creation approved; passive ingestion remains disabled here";
3036
3055
  var CREATE_INACTIVE_REJECTED = "[prim] decision not created: Decision ingestion is disabled here; rerun with prim's --yes to approve this one Decision, or run `prim enable` in a Git project";
3037
3056
  var splitList = (value) => (value ?? "").split(",").map((item) => item.trim()).filter(Boolean);
3057
+ var collectItem = (value, previous) => previous.concat(value);
3058
+ var collectPaths = (value, previous = []) => previous.concat(splitList(value));
3038
3059
  function registerDecisionsCommands(program2) {
3039
3060
  const decisions = program2.command("decisions").description("Inspect the project Decision Graph");
3040
3061
  decisions.command("check").description("Look up active decisions that reference one or more file paths").requiredOption(
3041
3062
  "--files <files>",
3042
- "Comma-separated file paths to check against the Decision Graph"
3063
+ "Comma-separated file paths to check against the Decision Graph (repeatable)",
3064
+ collectPaths
3043
3065
  ).action(async (opts) => {
3044
- const filePaths = splitList(opts.files);
3045
- const result = await checkAffectedDecisions(filePaths);
3066
+ const result = await checkAffectedDecisions(opts.files);
3046
3067
  const warning = formatDecisionsWarning(result);
3047
3068
  if (warning) {
3048
3069
  console.error(warning);
@@ -3115,10 +3136,43 @@ function registerDecisionsCommands(program2) {
3115
3136
  ).option("--kind <kind>", "change | exploration | task_execution | unclear (default change)").option("--rationale <text>", "Why the decision was made").option(
3116
3137
  "--area <area>",
3117
3138
  "Functional area (auth, data, infra, ui, api, billing, mobile, docs, testing)"
3118
- ).option("--decided <items>", "Comma-separated option(s) chosen").option("--alternatives <items>", "Comma-separated options considered but rejected").option("--confidence <level>", "high | medium | low (default high)").option("--reversibility <level>", "high | low (default high)").option(
3139
+ ).option(
3140
+ "--decided <item>",
3141
+ "One adopted constraint, verbatim; repeat the flag for each bullet",
3142
+ collectItem,
3143
+ []
3144
+ ).option(
3145
+ "--alternatives <item>",
3146
+ "One rejected option, verbatim; repeat the flag for each",
3147
+ collectItem,
3148
+ []
3149
+ ).option("--confidence <level>", "high | medium | low (default high)").option("--reversibility <level>", "high | low (default high)").option(
3119
3150
  "--files <paths>",
3120
- "Comma-separated repo-relative paths this decision governs (the files Conflict Gates would check \u2014 not currently enabled)"
3151
+ "Comma-separated exact repo-relative paths this decision governs (repeatable)",
3152
+ collectPaths
3121
3153
  ).action(async (opts, command) => {
3154
+ const requestedFiles = opts.files ?? [];
3155
+ let explicitScope = {};
3156
+ if (requestedFiles.length > 0) {
3157
+ const binding = repoSyncId(process.cwd());
3158
+ const root = canonicalGitRoot(process.cwd());
3159
+ const canonical = requestedFiles.map(
3160
+ (path) => canonicalRepositoryPath(path, root ?? process.cwd(), root)
3161
+ );
3162
+ if (!binding || canonical.some((path) => !path)) {
3163
+ console.error(
3164
+ "[prim] scoped decision rejected: run `prim enable` and provide exact in-repository file paths"
3165
+ );
3166
+ console.log(JSON.stringify({ ok: false, error: "invalid_repository_scope" }, null, 2));
3167
+ process.exitCode = EXIT_USAGE;
3168
+ return;
3169
+ }
3170
+ explicitScope = {
3171
+ protocolVersion: 3,
3172
+ repoSyncId: binding,
3173
+ files: canonical
3174
+ };
3175
+ }
3122
3176
  if (!isRepoActiveForCapture(process.cwd())) {
3123
3177
  const globals = command.optsWithGlobals();
3124
3178
  const nonInteractive = isNonInteractive(globals);
@@ -3137,11 +3191,11 @@ function registerDecisionsCommands(program2) {
3137
3191
  kind: opts.kind,
3138
3192
  rationale: opts.rationale,
3139
3193
  area: opts.area,
3140
- decided: splitList(opts.decided),
3141
- alternatives: splitList(opts.alternatives),
3194
+ decided: opts.decided,
3195
+ alternatives: opts.alternatives,
3142
3196
  confidence: opts.confidence,
3143
3197
  reversibility: opts.reversibility,
3144
- files: splitList(opts.files)
3198
+ ...explicitScope
3145
3199
  };
3146
3200
  try {
3147
3201
  const outcome = await fetchCreate(request);
@@ -3562,13 +3616,19 @@ var HERMES_ARGS = "--agent hermes";
3562
3616
  var EDIT_MATCHER = "write_file|patch";
3563
3617
  var JSON_INDENT3 = 2;
3564
3618
  var SHIM_MODE = 493;
3565
- var PRIM_BINS3 = [
3566
- CAPTURE_BIN3,
3567
- GATE_BIN3,
3568
- POST_TOOL_USE_BIN3,
3569
- SESSION_START_BIN3,
3570
- SESSION_END_BIN2
3571
- ];
3619
+ var PRIM_BINS3 = [CAPTURE_BIN3, GATE_BIN3, POST_TOOL_USE_BIN3, SESSION_START_BIN3, SESSION_END_BIN2];
3620
+ var PINNED_VERSION = packageVersion();
3621
+ if (!PINNED_VERSION) throw new Error("cannot determine Primitive package version");
3622
+ var sh = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
3623
+ var PINNED_NODE = sh(process.execPath);
3624
+ var PINNED_CASES = PRIM_BINS3.map((bin) => {
3625
+ const file = binFile(bin);
3626
+ if (!file) throw new Error(`cannot locate Primitive hook binary: ${bin}`);
3627
+ return ` ${bin})
3628
+ if [ -x ${PINNED_NODE} ] && [ -f ${sh(file)} ]; then exec ${PINNED_NODE} ${sh(file)} "$@"; fi
3629
+ exec npx --yes -p @primitive.ai/prim@${PINNED_VERSION} "$bin" "$@"
3630
+ ;;`;
3631
+ }).join("\n");
3572
3632
  function hermesHome() {
3573
3633
  return process.env.HERMES_HOME ?? join5(homedir5(), ".hermes");
3574
3634
  }
@@ -3580,17 +3640,13 @@ function shimPath() {
3580
3640
  }
3581
3641
  var SHIM_SCRIPT = `#!/bin/sh
3582
3642
  # prim Hermes hook shim \u2014 managed by \`prim hermes install\`. Hermes runs hooks
3583
- # with shell=False, so the PATH \u2192 local node_modules \u2192 npx @latest resolution a
3584
- # shell does for the Claude Code / Codex installs lives here instead.
3643
+ # with shell=False, so exact package entrypoints are selected here.
3585
3644
  bin="$1"
3586
3645
  shift
3587
- if command -v "$bin" >/dev/null 2>&1; then
3588
- exec "$bin" "$@"
3589
- fi
3590
- if [ -x "./node_modules/.bin/$bin" ]; then
3591
- exec "./node_modules/.bin/$bin" "$@"
3592
- fi
3593
- exec npx --yes -p @primitive.ai/prim@latest "$bin" "$@"
3646
+ case "$bin" in
3647
+ ${PINNED_CASES}
3648
+ esac
3649
+ exit 64
3594
3650
  `;
3595
3651
  function commandFor(bin) {
3596
3652
  return `"${shimPath()}" ${bin} ${HERMES_ARGS}`;
@@ -3899,13 +3955,7 @@ var PRIM_BLOCK_END = blockMarkers(PRE_COMMIT).end;
3899
3955
  var PRIM_MANAGED_MARK = "prim-managed-hook";
3900
3956
  var PRIM_CREATED_MARK = "prim-created-hook";
3901
3957
  function hookShim(binName) {
3902
- return `if command -v ${binName} >/dev/null 2>&1; then
3903
- ${binName} || true
3904
- elif [ -f "./node_modules/.bin/${binName}" ]; then
3905
- ./node_modules/.bin/${binName} || true
3906
- else
3907
- npx --yes -p @primitive.ai/prim ${binName} 2>/dev/null || true
3908
- fi`;
3958
+ return `{ ${pinnedHookCommand(binName)}; } || true`;
3909
3959
  }
3910
3960
  function gatedShim(binName) {
3911
3961
  return `if [ "$(git config --get prim.active 2>/dev/null)" = "true" ]; then
@@ -4170,7 +4220,6 @@ function installHooks(gitRoot, target) {
4170
4220
  installToDotGit(gitRoot, spec);
4171
4221
  }
4172
4222
  }
4173
- activateRepoBestEffort(gitRoot);
4174
4223
  }
4175
4224
  function registerHooksCommands(program2) {
4176
4225
  const hooks = program2.command("hooks").description("Manage git hooks");
@@ -4588,9 +4637,7 @@ async function performReconcile(idOrShortId, opts = {}) {
4588
4637
  process.exitCode = EXIT_SERVER;
4589
4638
  }
4590
4639
  function registerReconcileCommands(program2) {
4591
- program2.command("reconcile <idOrShortId>").description(
4592
- "Issue a single-use bypass for a decision flagged by Conflict Gates Enforcement (not currently enabled)"
4593
- ).option(
4640
+ program2.command("reconcile <idOrShortId>").description("Issue a single-use bypass for a decision flagged by Conflict Gates Enforcement").option(
4594
4641
  "--flag <conflictFlagId>",
4595
4642
  "Specific flag id to bind the bypass to (default: the decision's latest unack'd flag)"
4596
4643
  ).option("--json", "(reserved; STDOUT is always JSON)").action(async (idOrShortId, opts) => {
@@ -4712,9 +4759,12 @@ function planSetupSteps(opts) {
4712
4759
  });
4713
4760
  const skillArgs = ["skill", "install", "--agent", opts.agent, ...scopeArgs];
4714
4761
  steps.push({ key: "skill", label: "Agent skill", args: skillArgs, required: true });
4715
- if (opts.scope === "user") {
4716
- steps.push({ key: "enable", label: "Activate this repo", args: ["enable"], required: false });
4717
- }
4762
+ steps.push({
4763
+ key: "enable",
4764
+ label: "Bind and activate this repo",
4765
+ args: ["enable"],
4766
+ required: true
4767
+ });
4718
4768
  return steps;
4719
4769
  }
4720
4770
  var CONFLICT_SESSION = "session";
@@ -4870,7 +4920,7 @@ function registerSetupCommand(program2, dependencies = {}) {
4870
4920
  }
4871
4921
  }
4872
4922
  note("welcome");
4873
- results.welcome = run(["welcome"]).code === 0 ? "ok" : "failed";
4923
+ results.welcome = run(["welcome", "--agent", agent]).code === 0 ? "ok" : "failed";
4874
4924
  const failed = Object.entries(results).filter(([, v]) => v === "failed").map(([k]) => k);
4875
4925
  const trail = Object.entries(results).map(([k, v]) => `${k}:${v}`).join(" \xB7 ");
4876
4926
  note(
@@ -4976,28 +5026,58 @@ function registerStatuslineCommands(program2) {
4976
5026
  // src/commands/welcome.ts
4977
5027
  var CMD_GUTTER = 38;
4978
5028
  var RECENT_LIMIT = 5;
4979
- var REVERSE_PROMPT_LINES = [
4980
- "What are the most important goals in your organization that you're",
4981
- "responsible for, right now? What are you not focusing on, in order",
4982
- "to focus on those goals?"
4983
- ];
4984
- var REVERSE_PROMPT = REVERSE_PROMPT_LINES.join(" ");
4985
- var REVERSE_PROMPT_TEMPLATE = "We're interested in learning your current goals, as well as what you're not focusing on to achieve those goals. We found the following:\n\n$FOUND_GOALS\n\nHow do these look to you? Would you like to change these goals?";
4986
- var CALLOUT_TITLE = "Your turn";
4987
- var CALLOUT_INDENT = " ";
4988
- function ruledQuestion(lines) {
4989
- const prefix = `\u250C\u2500 ${CALLOUT_TITLE} `;
4990
- const width = Math.max(
4991
- `${prefix}\u2510`.length,
4992
- ...lines.map((line) => CALLOUT_INDENT.length + line.length + 1)
4993
- );
4994
- const top = `${prefix}${"\u2500".repeat(width - prefix.length - 1)}\u2510`;
4995
- const bottom = `\u2514${"\u2500".repeat(top.length - 2)}\u2518`;
5029
+ var WELCOME_AGENTS = ["claude", "codex", "hermes"];
5030
+ function resolveWelcomeAgent(agentFlag, env) {
5031
+ if (agentFlag !== void 0 && WELCOME_AGENTS.includes(agentFlag)) {
5032
+ return agentFlag;
5033
+ }
5034
+ if (agentFlag === void 0 && env.HERMES_INTERACTIVE) {
5035
+ return "hermes";
5036
+ }
5037
+ return void 0;
5038
+ }
5039
+ var CAPTURE_LINES = {
5040
+ claude: ["Capture is automatic \u2014 keep coding; your decisions are recorded for you."],
5041
+ codex: [
5042
+ "Capture starts once you trust the prim hooks \u2014 run /hooks in Codex;",
5043
+ "until then nothing is recorded."
5044
+ ],
5045
+ hermes: [
5046
+ "Capture starts once you approve the prim hooks when Hermes prompts;",
5047
+ "until then nothing is recorded."
5048
+ ],
5049
+ generic: [
5050
+ "Capture is automatic once your agent's prim hooks are active \u2014 keep",
5051
+ "coding; your decisions are recorded for you."
5052
+ ]
5053
+ };
5054
+ var PASSIVE_LINES = {
5055
+ claude: ["Otherwise Primitive captures decisions passively while you work."],
5056
+ codex: [
5057
+ "Once you trust the prim hooks (/hooks in Codex), Primitive captures",
5058
+ "decisions passively while you work."
5059
+ ],
5060
+ hermes: [
5061
+ "Once you approve the prim hooks, Primitive captures decisions",
5062
+ "passively while you work."
5063
+ ],
5064
+ generic: [
5065
+ "Once your agent's prim hooks are active, Primitive captures decisions",
5066
+ "passively while you work."
5067
+ ]
5068
+ };
5069
+ var GUIDANCE_PASSIVE = {
5070
+ claude: "Otherwise, Primitive passively captures decisions in the background while you work.",
5071
+ codex: "Otherwise, once you trust the prim hooks (run /hooks in Codex), Primitive passively captures decisions in the background while you work.",
5072
+ hermes: "Otherwise, once you approve the prim hooks when Hermes prompts, Primitive passively captures decisions in the background while you work.",
5073
+ generic: "Otherwise, once your agent's prim hooks are active, Primitive passively captures decisions in the background while you work."
5074
+ };
5075
+ function seedGuidanceFor(agent) {
4996
5076
  return [
4997
- color(top, "green"),
4998
- ...lines.map((line) => `${CALLOUT_INDENT}${line}`),
4999
- color(bottom, "green")
5000
- ];
5077
+ "You can tell your agent to add any decision to your Primitive decision graph at any time.",
5078
+ GUIDANCE_PASSIVE[agent ?? "generic"],
5079
+ "To capture decisions in other repositories, enable Primitive there too: run `prim enable` in each one."
5080
+ ].join(" ");
5001
5081
  }
5002
5082
  function welcomeStateFromRecent(result) {
5003
5083
  if (result.unavailable !== void 0) {
@@ -5010,9 +5090,14 @@ function welcomeStateFromRecent(result) {
5010
5090
  }
5011
5091
  return { org: "active", recent };
5012
5092
  }
5013
- function formatWelcome(state) {
5093
+ function formatWelcome(state, agent) {
5014
5094
  const cmd = (command, desc) => ` ${dim(command.padEnd(CMD_GUTTER))}${desc}`;
5015
5095
  const bullet = (text) => ` ${color("\u2022", "green")} ${text}`;
5096
+ const asBullet = ([first, ...rest]) => [
5097
+ bullet(first),
5098
+ ...rest.map((line) => ` ${line}`)
5099
+ ];
5100
+ const copyKey = agent ?? "generic";
5016
5101
  const head = [
5017
5102
  bold(color("Welcome to Primitive", "green")),
5018
5103
  "",
@@ -5020,10 +5105,9 @@ function formatWelcome(state) {
5020
5105
  "shared graph \u2014 automatically, as you work.",
5021
5106
  "",
5022
5107
  bold("How it works"),
5023
- bullet("Capture is automatic \u2014 keep coding; your decisions are recorded for you."),
5024
- bullet("Conflict Gates (with Enforcement) flag or block edits that conflict"),
5025
- " with a load-bearing decision \u2014 not currently enabled. Contact",
5026
- " support@getprimitive.ai to turn them on for your team.",
5108
+ ...asBullet(CAPTURE_LINES[copyKey]),
5109
+ bullet("Conflict Gates can warn, ask, or block edits that conflict"),
5110
+ " with a load-bearing decision when Enforcement is enabled.",
5027
5111
  bullet('Occasional yes/no prompts confirm the "why" behind a decision \u2014'),
5028
5112
  " answering keeps the graph trustworthy.",
5029
5113
  ""
@@ -5043,10 +5127,16 @@ function formatWelcome(state) {
5043
5127
  body = [
5044
5128
  ...teamContext,
5045
5129
  bold("Let's seed your decision graph"),
5046
- "You haven't recorded a decision yet \u2014 answer this and I'll record",
5047
- "each goal as a decision:",
5130
+ "You haven't recorded a decision yet. Your agent will look through",
5131
+ "what you've told it and this repo's shared memory files, and propose",
5132
+ "any decisions it finds \u2014 approve, revise, or reject each one, and",
5133
+ 'share the "why" when it asks. Approved decisions become part of',
5134
+ "your team's shared graph, visible to any teammates.",
5048
5135
  "",
5049
- ...ruledQuestion(REVERSE_PROMPT_LINES)
5136
+ bold("From here on"),
5137
+ bullet("Tell your agent to add any decision to Primitive, any time."),
5138
+ ...asBullet(PASSIVE_LINES[copyKey]),
5139
+ bullet("Run `prim enable` in each additional repo you want captured.")
5050
5140
  ];
5051
5141
  } else {
5052
5142
  body = [
@@ -5056,10 +5146,9 @@ function formatWelcome(state) {
5056
5146
  cmd("prim --help", "everything else")
5057
5147
  ];
5058
5148
  }
5059
- const footer = state.org === "seed" ? [] : ["", dim("App: https://app.getprimitive.ai")];
5060
- return [...head, ...body, ...footer].join("\n");
5149
+ return [...head, ...body, "", dim("App: https://app.getprimitive.ai")].join("\n");
5061
5150
  }
5062
- function welcomeJson(state) {
5151
+ function welcomeJson(state, agent) {
5063
5152
  if (state.org === "active") {
5064
5153
  return { welcomed: true, org: "active", recent: state.recent };
5065
5154
  }
@@ -5067,20 +5156,23 @@ function welcomeJson(state) {
5067
5156
  return {
5068
5157
  welcomed: true,
5069
5158
  org: "seed",
5070
- reversePrompt: REVERSE_PROMPT,
5071
- reversePromptTemplate: REVERSE_PROMPT_TEMPLATE,
5159
+ seedGuidance: seedGuidanceFor(agent),
5072
5160
  recent: state.recent
5073
5161
  };
5074
5162
  }
5075
5163
  return { welcomed: true, org: "unknown" };
5076
5164
  }
5077
5165
  function registerWelcomeCommand(program2, deps = { getClient }) {
5078
- program2.command("welcome").description("Print a brief orientation to Primitive's decision graph").action(async () => {
5166
+ program2.command("welcome").description("Print a brief orientation to Primitive's decision graph").option(
5167
+ "--agent <agent>",
5168
+ "claude, codex, or hermes \u2014 tailors the capture guidance (Hermes auto-detected)"
5169
+ ).action(async (options) => {
5170
+ const agent = resolveWelcomeAgent(options.agent, process.env);
5079
5171
  const result = await fetchRecent({ limit: RECENT_LIMIT }, deps);
5080
5172
  const state = welcomeStateFromRecent(result);
5081
- process.stderr.write(`${formatWelcome(state)}
5173
+ process.stderr.write(`${formatWelcome(state, agent)}
5082
5174
  `);
5083
- printJson(welcomeJson(state));
5175
+ printJson(welcomeJson(state, agent));
5084
5176
  });
5085
5177
  }
5086
5178
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/commands/statusline.ts
4
4
  import { readFileSync as readFileSync5 } from "fs";
5
- import { dirname as dirname4, resolve as resolve2 } from "path";
5
+ import { dirname as dirname5, resolve as resolve3 } from "path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
7
 
8
8
  // src/client.ts
@@ -63,7 +63,7 @@ function daemonRequest(method, params = {}, opts = {}) {
63
63
  const id = nextRequestId++;
64
64
  const request = { id, method, params };
65
65
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
66
- return new Promise((resolve3) => {
66
+ return new Promise((resolve4) => {
67
67
  let settled = false;
68
68
  let socket;
69
69
  let buffer = "";
@@ -78,7 +78,7 @@ function daemonRequest(method, params = {}, opts = {}) {
78
78
  } catch {
79
79
  }
80
80
  }
81
- resolve3(value);
81
+ resolve4(value);
82
82
  };
83
83
  const timer = setTimeout(() => settle(null), timeoutMs);
84
84
  timer.unref();
@@ -140,6 +140,8 @@ import { join as join4 } from "path";
140
140
 
141
141
  // src/lib/git.ts
142
142
  import { execFileSync } from "child_process";
143
+ import { lstatSync, realpathSync } from "fs";
144
+ import { basename, dirname as dirname3, isAbsolute, relative, resolve as resolve2, sep } from "path";
143
145
  function gitToplevel(cwd) {
144
146
  try {
145
147
  return execFileSync("git", ["rev-parse", "--show-toplevel"], {
@@ -202,7 +204,7 @@ import { join as join6 } from "path";
202
204
 
203
205
  // src/lib/bin-path.ts
204
206
  import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
205
- import { dirname as dirname3, isAbsolute, join as join5 } from "path";
207
+ import { dirname as dirname4, isAbsolute as isAbsolute2, join as join5 } from "path";
206
208
  import { fileURLToPath } from "url";
207
209
  var PKG_NAME = "@primitive.ai/prim";
208
210
  var NPX_FALLBACK = `npx --yes -p ${PKG_NAME}@latest`;
@@ -271,13 +273,13 @@ var STATUSLINE_TIMEOUT_MS = 200;
271
273
  var STATUSLINE_NAME_CAP = 3;
272
274
  function readPackageVersion() {
273
275
  try {
274
- const here = dirname4(fileURLToPath2(import.meta.url));
276
+ const here = dirname5(fileURLToPath2(import.meta.url));
275
277
  const candidates = [
276
278
  // The supervised runtime stages a tiny manifest beside the standalone
277
279
  // statusline bundle, so it remains versioned without loading the package.
278
- resolve2(here, "manifest.json"),
279
- resolve2(here, "../../package.json"),
280
- resolve2(here, "../package.json")
280
+ resolve3(here, "manifest.json"),
281
+ resolve3(here, "../../package.json"),
282
+ resolve3(here, "../package.json")
281
283
  ];
282
284
  for (const path of candidates) {
283
285
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitive.ai/prim",
3
- "version": "0.1.0-alpha.56",
3
+ "version": "0.1.0-alpha.58",
4
4
  "description": "CLI for Primitive's decision graph — passive decision capture, conflict gate, and team presence",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -62,6 +62,7 @@
62
62
  "dependencies": {
63
63
  "commander": "^12.1.0",
64
64
  "diff": "^5.2.0",
65
+ "unbash": "4.0.3",
65
66
  "update-notifier": "^7.3.1",
66
67
  "yaml": "^2.9.0"
67
68
  },