argus-decision-mcp 1.3.0 → 1.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.
package/README.md CHANGED
@@ -17,7 +17,11 @@ graded you. Reality did.
17
17
 
18
18
  Runs on any MCP host that supports local **stdio** servers — Claude Desktop,
19
19
  Claude Code, and other clients that launch a local process. (Remote-only
20
- connectors that require an HTTP transport aren't supported yet — see the roadmap.)
20
+ connectors that require an HTTP transport aren't supported yet.)
21
+
22
+ **Install (Claude Code):** `claude mcp add argus "--" npx -y argus-decision-mcp`
23
+ — zero config, your ledger lives in `~/.argus`. Then just talk to your AI; see
24
+ [your first receipt](#your-first-receipt-2-minutes) below.
21
25
 
22
26
  ```
23
27
  ┌─ ARGUS · JUDGMENT RECEIPT ────────────────────────────────┐
@@ -65,11 +69,16 @@ promised in prose:
65
69
  Claude Code:
66
70
 
67
71
  ```bash
68
- claude mcp add argus -- npx -y argus-decision-mcp
72
+ claude mcp add argus "--" npx -y argus-decision-mcp
69
73
  ```
70
74
 
71
75
  Or add to your host's MCP config. **Zero config works**: with no `env` at all,
72
- your ledger lives in `~/.argus`.
76
+ your ledger lives in `~/.argus`. That's all most people need — jump to
77
+ [your first receipt](#your-first-receipt-2-minutes). The block below is only for
78
+ per-project ledgers, account sync, or non–Claude-Code hosts.
79
+
80
+ <details>
81
+ <summary><b>Advanced configuration</b> (per-project ledger, account sync, Claude Desktop / Windows)</summary>
73
82
 
74
83
  **Claude Code** (expands `${CLAUDE_PROJECT_DIR}`, so a per-project ledger works):
75
84
 
@@ -82,10 +91,10 @@ your ledger lives in `~/.argus`.
82
91
  "env": {
83
92
  // OPTIONAL — per-project ledger. Omit entirely to use ~/.argus.
84
93
  "ARGUS_DIR": "${CLAUDE_PROJECT_DIR}/.argus",
85
- // OPTIONAL — connect to your Argus account so sealed predictions get an
86
- // email at their check-by date (the Companion Brief) and show up in the
87
- // web dashboard. Issue the token in the web app (Settings → sync token).
88
- // Leave it unset to stay fully local (the privacy-preserving default).
94
+ // OPTIONAL — connect to your Argus account so saved predictions get an
95
+ // email at their check-by date and show up in the web dashboard. Issue
96
+ // the token at https://argus.voyage (Settings → sync token). Leave it
97
+ // unset to stay fully local (the privacy-preserving default).
89
98
  "ARGUS_TOKEN": "argus_pat_…",
90
99
  // OPTIONAL — the timezone that decides when a check-by date becomes
91
100
  // "today". Unset = your machine's local timezone (usually right).
@@ -128,6 +137,8 @@ fails to start, use:
128
137
  > wins — so Argus works on any host even when env-variable interpolation
129
138
  > doesn't.
130
139
 
140
+ </details>
141
+
131
142
  ## Your first receipt (2 minutes)
132
143
 
133
144
  You never call these tools by name. Once the server is connected, you just
@@ -228,10 +239,10 @@ See [SECURITY.md](SECURITY.md).
228
239
  ## Measured
229
240
 
230
241
  The structural claims are tested, not asserted — `npm test` runs deterministic
231
- gates (no verdict tool exists, settle-without-seal refused, path traversal
232
- blocked, receipts carry `ai_verdict: null`). A model-in-the-loop spine eval
233
- (`npm run eval`, 12 scenarios, opus judge) measured, across Sonnet 4.6 and
234
- Haiku 4.5:
242
+ gates (no verdict tool exists, recording a result before a prediction is saved
243
+ is refused, path traversal blocked, receipts carry `ai_verdict: null`). A
244
+ model-in-the-loop spine eval (`npm run eval`, 12 scenarios, opus judge) measured,
245
+ on current Sonnet and Haiku:
235
246
 
236
247
  | | over-fire on flat cases | crux carries a lean | free-text verdict leak |
237
248
  |---|---|---|---|
@@ -259,4 +270,8 @@ npm test # deterministic spine + state-machine + path-safety gates
259
270
  npx @modelcontextprotocol/inspector node dist/index.js
260
271
  ```
261
272
 
262
- MIT licensed.
273
+ ## Links
274
+
275
+ - **Web app** (nothing to install): https://argus.voyage
276
+ - **Source & issues**: https://github.com/commet/Argus
277
+ - **License**: MIT.
@@ -26,7 +26,16 @@ const KO_ERRORS = {
26
26
  EXTRACT_FAILED: { message: '문서에서 텍스트를 추출하지 못했습니다.', recovery: '문서 내용을 text에 붙여 넣거나 markdown/txt로 변환하세요.' },
27
27
  EMPTY: { message: '검수할 수 있는 문서 내용이 없습니다.', recovery: '20자 이상의 text 또는 읽을 수 있는 file_path를 전달하세요.' },
28
28
  OUTCOME_REQUIRED: { message: '현실에서 실제로 어떻게 됐는지 결과가 필요합니다.', recovery: '사용자에게 결과를 물어 outcome에 전달하세요. 결과를 추론하지 마세요.' },
29
- PREMATURE_SETTLE: { message: '아직 확인일이 되지 않았습니다.', recovery: '확인일까지 기다리거나 일정이 바뀌었다면 확인일을 수정하세요.' },
29
+ PREMATURE_SETTLE: { message: '아직 확인일이 되지 않았습니다.', recovery: '확인일까지 기다리세요. 일정이 바뀌었다면 outcome="still_pending"에 defer_to로 새 확인일을 전달하면 됩니다.' },
30
+ // ko/en 패리티: 아래 코드들은 en에서만 상세했고 ko는 제네릭 폴백이었다 —
31
+ // 한국어 사용자가 같은 품질의 복구 안내를 받도록 전용 문구를 둔다.
32
+ NO_PRIOR_SEAL: { message: '이 id로 저장된 예측이 없습니다.', recovery: 'argus_predict로 반증 가능한 예측과 확인일을 먼저 저장하세요. (id가 argus_settings sync에서 온 "mcp_" 접두사라면 접두사를 뗀 id를 쓰세요.)' },
33
+ BAD_CHECK_BY: { message: '확인일이 오늘 이후의 날짜(YYYY-MM-DD)가 아닙니다.', recovery: 'check_by를 오늘 이후 날짜로 다시 전달하세요.' },
34
+ ILLEGAL_TRANSITION: { message: '지금 상태에서는 할 수 없는 작업입니다.', recovery: 'argus_patterns view="all"로 이 결정의 현재 상태를 확인한 뒤 맞는 도구를 사용하세요.' },
35
+ ALREADY_SETTLED: { message: '이미 실제 결과가 기록된 결정입니다.', recovery: '영수증은 argus_patterns view="receipt"로 볼 수 있습니다. 새 결정이면 새 id로 여세요.' },
36
+ DECISION_CLOSED: { message: '접힌(닫힌) 결정이라 더 진행할 수 없습니다.', recovery: '필요하면 새 id로 다시 여세요. 닫힌 기록은 그대로 남습니다.' },
37
+ GOALPOST_MOVED: { message: '봉인된 예측 문장은 확인일 전에 바꿀 수 없습니다.', recovery: '일정 변경은 outcome="still_pending"과 defer_to로, 예측 자체가 달라졌다면 새 결정으로 여세요.' },
38
+ NO_SUCH_PREMISE: { message: '해당 번호의 전제를 찾지 못했습니다.', recovery: 'argus_patterns view="decision_context"로 전제 목록과 번호를 확인하세요.' },
30
39
  WHAT_HAPPENED_REQUIRED: { message: '실제로 일어난 일을 기록해야 합니다.', recovery: '사용자에게 실제 결과를 물어 what_happened에 그대로 전달하세요.' },
31
40
  DEFER_DATE_REQUIRED: { message: '다시 확인할 날짜가 필요합니다.', recovery: '사용자에게 날짜를 물어 defer_to에 YYYY-MM-DD로 전달하세요. 더는 중요하지 않다면 argus_capture action="close"를 사용하세요.' },
32
41
  NOT_CONNECTED: { message: '이 터미널은 Argus 계정과 연결돼 있지 않습니다.', recovery: '웹 설정에서 동기화 토큰을 발급하고 MCP 설정의 ARGUS_TOKEN에 넣으세요.' },
@@ -103,12 +112,18 @@ export function localizeToolResult(args, result) {
103
112
  if (!sc || sc['ok'] !== false)
104
113
  return result;
105
114
  const code = String(sc['error_code'] ?? 'INTERNAL_ERROR');
106
- const copy = code === 'INVALID_INPUT' && Array.isArray(sc['invalid_fields'])
115
+ let copy = code === 'INVALID_INPUT' && Array.isArray(sc['invalid_fields'])
107
116
  ? localizeInvalidInput(sc['invalid_fields'])
108
117
  : KO_ERRORS[code] ?? {
109
118
  message: '요청을 처리하지 못했습니다.',
110
119
  recovery: '입력값과 현재 결정 상태를 확인한 뒤 다시 시도하세요.',
111
120
  };
121
+ // en에만 있던 날짜 상세를 ko에서도 보존 — "언제가 확인일인데?"에 답이 되도록.
122
+ if (code === 'PREMATURE_SETTLE') {
123
+ const m = String(sc['message'] ?? '').match(/check-by (\d{4}-\d{2}-\d{2}), today (\d{4}-\d{2}-\d{2})/);
124
+ if (m)
125
+ copy = { message: `아직 확인일이 되지 않았습니다 (확인일 ${m[1]} · 오늘 ${m[2]}).`, recovery: copy.recovery };
126
+ }
112
127
  const localized = {
113
128
  ...sc,
114
129
  message: copy.message,
@@ -2,10 +2,9 @@
2
2
  * Argus Document Judgment Review — Phase 0 Schema Lock.
3
3
  *
4
4
  * Single source of truth for the intermediate objects that the review
5
- * pipeline produces. Per the design doc
6
- * (docs/strategy/argus-product-loop-ux-subtask-b-2026-07-01.md "구현 순서 /
7
- * Phase 0: Schema Lock"): fix the objects BEFORE the pipeline and the UI, so
8
- * the same `JudgmentReceipt` can be produced by the webapp and the MCP/plugin.
5
+ * pipeline produces. Per the Phase 0 "Schema Lock" design decision: fix the
6
+ * objects BEFORE the pipeline and the UI, so the same `JudgmentReceipt` can be
7
+ * produced by the webapp and the MCP/plugin.
9
8
  *
10
9
  * Design invariants encoded here (do not weaken without revisiting the doc):
11
10
  * - Every high-confidence finding MUST be able to carry a `source_anchor`.
@@ -88,7 +88,7 @@ export const SURFACES = {
88
88
  },
89
89
  checkin: {
90
90
  nothing_due: 'Nothing is due right now.',
91
- account_hint: ' This screen reads the local ledger only. Predictions saved in your account show up with argus_settings action=sync.',
91
+ account_hint: ' This screen reads the local decision record only. Predictions saved in your account show up with argus_settings action=sync.',
92
92
  upcoming: (n, days) => ` ${n} coming due within ${days} day(s). Informational; no result is due yet.`,
93
93
  due_contracts: (n) => `${n} saved prediction(s) past check-by. Time to record what happened (argus_resolve).`,
94
94
  anchor_mirror: (days, n, words) => `${days} day(s) since you saved this, and ${n} prediction(s) are past check-by. Back then you wrote: '${words}' All that's left is to record what actually happened (argus_resolve).`,
@@ -230,14 +230,14 @@ export const SURFACES = {
230
230
  },
231
231
  checkin: {
232
232
  nothing_due: '지금 확인할 차례가 된 것은 없습니다.',
233
- account_hint: ' 이 화면은 로컬 원장만 읽습니다. 계정에 저장한 예측은 argus_settings action=sync로 가져올 수 있습니다.',
233
+ account_hint: ' 이 화면은 로컬 판단 기록만 읽습니다. 계정에 저장한 예측은 argus_settings action=sync로 가져올 수 있습니다.',
234
234
  upcoming: (n, days) => ` ${days}일 안에 확인일이 오는 예측이 ${n}건 있습니다. 아직 결과를 기록할 때는 아닙니다.`,
235
235
  due_contracts: (n) => `저장한 예측 ${n}건이 확인일을 지났습니다. 실제 결과를 기록할 차례입니다 (argus_resolve).`,
236
236
  anchor_mirror: (days, n, words) => `예측을 저장한 지 ${days}일이 지났고, ${n}건이 확인일을 넘겼습니다. 그때 당신은 이렇게 적었습니다: '${words}' 실제로 어떻게 됐는지만 기록하면 됩니다 (argus_resolve).`,
237
237
  due_premises: (n, staleDays, sinceAdd) => `전제 사실 ${n}건을 다시 확인할 차례입니다${n === 1 && staleDays != null ? ` (마지막 확인 후 ${staleDays}일)` : n === 1 && staleDays === null && sinceAdd != null ? ` (적어둔 지 ${sinceAdd}일, 아직 첫 확인 전)` : ''} (argus_capture action=update_fact).`,
238
238
  reconsider_one: (days, q) => `${days === null ? '얼마 전' : `${days}일 전`}에 미결로 남겨둔 질문입니다: '${q}' 지금 답할 수 있으면 답하고, 그대로 열어두어도 괜찮습니다 (argus_capture).`,
239
239
  reconsider_more: (n) => `미결로 남겨둔 질문 ${n}건을 다시 볼 차례입니다 (argus_capture).`,
240
- dropped_lines: (n) => ` 원장에서 읽지 못한 줄이 ${n}개 있습니다 (크래시 흔적일 수 있습니다). 기록은 추가만 하는 방식이라 나머지는 안전합니다. ledger.jsonl을 백업해 두세요.`,
240
+ dropped_lines: (n) => ` 판단 기록에서 읽지 못한 줄이 ${n}개 있습니다 (크래시 흔적일 수 있습니다). 기록은 추가만 하는 방식이라 나머지는 안전합니다. ledger.jsonl을 백업해 두세요.`,
241
241
  watch_mirror: (date, text) => `${date}에 이렇게 적으셨습니다: '${text}' 그 뒤로 어떻게 됐나요?`,
242
242
  fleet_summary: (projects, due) => ` 다른 프로젝트 ${projects}곳에 확인할 차례가 ${due}건 있습니다. 자세한 내용은 data.fleet에 있고, 결과는 각 프로젝트에서 기록합니다.`,
243
243
  },
@@ -245,11 +245,11 @@ export const SURFACES = {
245
245
  live_with_due: (total, due) => `계정에 살아 있는 예측 ${total}개 중 ${due}개가 확인할 차례입니다. ` +
246
246
  '터미널에서 저장한 예측은 local_id와 argus_resolve로 결과를 기록하고, 웹에서 저장한 예측은 웹 대시보드에서 기록하세요.',
247
247
  live_no_due: (total) => `계정에 살아 있는 예측 ${total}개. 확인할 차례가 된 것은 없습니다.`,
248
- settled_on_web: (n) => ` 웹에서 이미 결과를 기록한 예측이 ${n}건 있습니다. argus_settings action=sync에 import_settlements:true를 주면 웹 기록을 원장으로 옮겨옵니다 (argus_resolve로 직접 적어도 됩니다).`,
248
+ settled_on_web: (n) => ` 웹에서 이미 결과를 기록한 예측이 ${n}건 있습니다. argus_settings action=sync에 import_settlements:true를 주면 웹 기록을 로컬 판단 기록으로 가져옵니다 (argus_resolve로 직접 적어도 됩니다).`,
249
249
  unclear_on_web: (n) => ` 계정에서 ${n}건이 "불분명"으로 표시돼 있습니다. 현실이 아직 답하지 않아 가져오지 않았습니다. 결과가 기록되기 전까지 여기서는 계속 확인 대상입니다.`,
250
250
  pushed_up: (n) => ` 계정이 못 받은 변경 ${n}건을 올려보냈습니다. 결과를 기록했거나, 접었거나, 날짜를 옮긴 것들입니다. 이미 처리한 건에 대해 더는 알림이 오지 않습니다.`,
251
251
  push_up_failed: (n) => ` 로컬 변경 ${n}건이 아직 계정에 닿지 않았습니다. 그 건들에 대해 메일이 계속 올 수 있습니다. 여기 기록은 그대로 유효하니, 온라인일 때 argus_settings action=sync를 다시 실행하세요.`,
252
- imported: (n) => ` 웹에서 기록한 결과 ${n}건을 원장으로 옮겨왔습니다. 당신이 적은 그대로입니다.`,
252
+ imported: (n) => ` 웹에서 기록한 결과 ${n}건을 로컬 판단 기록으로 가져왔습니다. 당신이 적은 그대로입니다.`,
253
253
  truncation: (shown, matched) => `${matched}개 중 ${shown}개만 표시합니다. limit을 올리거나 due_only로 좁히세요.`,
254
254
  },
255
255
  seal: {
@@ -143,11 +143,34 @@ async function send(event, env) {
143
143
  clearTimeout(timer);
144
144
  }
145
145
  }
146
+ /**
147
+ * Privacy-first, OPT-IN invite for users who have NOT enabled telemetry. Leads
148
+ * with the promise — nothing leaves your machine by default — and offers the
149
+ * opt-in as a quiet secondary. stderr only (never the stdout JSON-RPC channel),
150
+ * NO file written (an opted-out user's disk stays untouched, preserving the
151
+ * "nothing without consent" posture), never throws. Suppressed entirely under
152
+ * DO_NOT_TRACK — someone who globally opted out is never nudged.
153
+ */
154
+ export function inviteTelemetryOptIn(env = process.env) {
155
+ const dnt = (env.DO_NOT_TRACK || '').trim().toLowerCase();
156
+ if (ON_VALUES.has(dnt))
157
+ return; // globally opted out — do not invite
158
+ try {
159
+ process.stderr.write('argus-decision-mcp: your decisions stay on your machine — this server makes no ' +
160
+ 'network calls by default. Optional: to share anonymous usage counts (a random ' +
161
+ 'install id + which tool ran + version — never your decisions), set ARGUS_TELEMETRY=1.\n');
162
+ }
163
+ catch {
164
+ /* stderr unavailable — harmless */
165
+ }
166
+ }
146
167
  /** Fire-and-forget process-activation signal. Never blocks, never throws. */
147
168
  export function recordServerStart(env = process.env) {
148
169
  try {
149
- if (!telemetryEnabled(env))
170
+ if (!telemetryEnabled(env)) {
171
+ inviteTelemetryOptIn(env); // once at startup: privacy promise + opt-in offer
150
172
  return;
173
+ }
151
174
  maybeShowNotice();
152
175
  void send(buildEvent('server_start', telemetryInstallId()), env);
153
176
  }
package/dist/resources.js CHANGED
@@ -19,7 +19,7 @@ import { duePremises, groupDuePremises, isMonitored, isDueForRecheck } from './l
19
19
  */
20
20
  const JSON_MIME = 'application/json';
21
21
  export const STATIC_RESOURCES = [
22
- { uri: 'argus://ledger', name: 'Argus 원장 · Ledger', description: '모든 결정의 재생된 전체 상태 · Full replayed state of all decisions (stats, contracts, integrity).', mimeType: JSON_MIME },
22
+ { uri: 'argus://ledger', name: 'Argus 판단 기록 · Decision record', description: '모든 결정의 현재 상태와 이력 · Current state and history of all decisions (stats, contracts, integrity).', mimeType: JSON_MIME },
23
23
  { uri: 'argus://contracts/due', name: '결과를 확인할 예측 · Predictions due', description: '확인일이 지난 예측 · Saved predictions past their check date.', mimeType: JSON_MIME },
24
24
  { uri: 'argus://bearing/current', name: '결과를 기다리는 예측 · Predictions awaiting results', description: '저장됐고 아직 실제 결과가 기록되지 않은 예측 · Saved predictions with no recorded result yet.', mimeType: JSON_MIME },
25
25
  { uri: 'argus://premises/due', name: '재확인할 전제 · Premises due', description: '현실과 다시 확인할 때가 된 추적 전제 · Monitored premises due for a reality re-check.', mimeType: JSON_MIME },
@@ -239,7 +239,7 @@ export const checkIn = {
239
239
  ok: true, tool: 'argus_check_in',
240
240
  surface: mirrorLine + S.nothing_due + accountHint + upcomingLine + fleetLine + integrityLine,
241
241
  next_actions: ['stop'],
242
- data: { due: [], due_count: 0, due_premises: [], due_premise_count: 0, due_open_questions: [], due_open_question_count: 0, ...(upDays > 0 ? { upcoming } : {}), ...(a['fleet'] === true ? { fleet: fleetRows } : {}), ...watchData, today, capture_status: captureStatus, v2_brief: readV2Brief(dir, today), v2_divergence: briefDivergence([], readV2Brief(dir, today)) },
242
+ data: { due: [], due_count: 0, due_premises: [], due_premise_count: 0, due_open_questions: [], due_open_question_count: 0, ...(upDays > 0 ? { upcoming } : {}), ...(a['fleet'] === true ? { fleet: fleetRows } : {}), ...watchData, today, ...(process.env['ARGUS_V2_DEBUG'] === '1' ? { capture_status: captureStatus, v2_brief: readV2Brief(dir, today), v2_divergence: briefDivergence([], readV2Brief(dir, today)) } : {}) },
243
243
  });
244
244
  }
245
245
  const parts = [];
@@ -282,13 +282,16 @@ export const checkIn = {
282
282
  ...(upDays > 0 ? { upcoming } : {}),
283
283
  ...(a['fleet'] === true ? { fleet: fleetRows } : {}),
284
284
  ...watchData,
285
- today, integrity: ledger.integrity, capture_status: captureStatus,
286
- // v2 병기 (P2-3): v1이 여전히 정본이고 surface는 무접촉 — 관찰용.
287
- // P2 읽기 전환 전에 원장의 답이 갈리는지 실사용에서 드러낸다.
288
- v2_brief: readV2Brief(dir, today),
289
- // 발산 감지 (M-잔여-1): due id 집합의 대칭차 — 관찰 기간 발산 0이
290
- // 읽기 전환의 조건이다. dueAll(전수)로 대조, 표시 상한과 무관.
291
- v2_divergence: briefDivergence(dueAll.map((d) => d.id), readV2Brief(dir, today)),
285
+ today, integrity: ledger.integrity,
286
+ // v2 병기/진단은 ARGUS_V2_DEBUG=1 뒤로. 공개 payload에 싣던 v2_brief가
287
+ // 머신-전역 durable-home 저장소를 읽어 다른 프로젝트의 결정 원문을
288
+ // 모든 프로젝트 대화에 노출했다(교차-프로젝트 누출) — 관찰용 진단은
289
+ // 옵트인 디버그로만. capture_status도 사용자-무의미 진단이라 동거.
290
+ ...(process.env['ARGUS_V2_DEBUG'] === '1' ? {
291
+ capture_status: captureStatus,
292
+ v2_brief: readV2Brief(dir, today),
293
+ v2_divergence: briefDivergence(dueAll.map((d) => d.id), readV2Brief(dir, today)),
294
+ } : {}),
292
295
  },
293
296
  });
294
297
  }
@@ -288,10 +288,10 @@ async function opAdd(dir, id, today, now, state, existing, a) {
288
288
  // count + ref range (echoing five sentences would bury the confirmation).
289
289
  : (events.length === 1
290
290
  ? (ko
291
- ? `방금 적어뒀어요: '${oneLine(echo[0]?.text ?? '')}'. 고칠 있으면 op=amend로 바꿀 있어요.${monitoredNote}`
291
+ ? `방금 적어뒀어요: '${oneLine(echo[0]?.text ?? '')}'. 잘못 적혔으면 그대로 말해 주세요 바로잡은 내용도 기록에 남아요.${monitoredNote}`
292
292
  : `Noted: "${oneLine(echo[0]?.text ?? '')}". Fix anything wrong with argus_capture.${monitoredNote}`)
293
293
  : (ko
294
- ? `전제 ${events.length}건을 기록했습니다 (${refRange}). 틀린 것이 있으면 op=amend로 고치세요. 고친 내용도 기록에 남습니다.${monitoredNote}`
294
+ ? `전제 ${events.length}건을 기록했습니다 (${refRange}). 틀린 것이 있으면 말해 주세요 바로잡은 내용도 기록에 남습니다.${monitoredNote}`
295
295
  : `${events.length} premise(s) recorded (${refRange}). Fix anything wrong with argus_capture; your correction stays on the record too.${monitoredNote}`));
296
296
  return envelope({
297
297
  ok: true, tool: 'argus_premises',
@@ -147,7 +147,7 @@ const settingsSchema = z.discriminatedUnion('action', [
147
147
  argus_dir: zArgusDir,
148
148
  action: z.literal('sync').describe('로컬 기록과 Argus 계정 기록을 지금 동기화합니다.'),
149
149
  due_only: z.boolean().default(false).describe('확인일이 된 기록만 가져옵니다.'),
150
- import_settlements: z.boolean().default(true).describe('웹에서 기록한 실제 결과를 로컬 원장에 반영합니다.'),
150
+ import_settlements: z.boolean().default(true).describe('웹에서 기록한 실제 결과를 로컬 판단 기록에 반영합니다.'),
151
151
  push_local: z.boolean().default(true).describe('계정에 닿지 못한 로컬 변경을 다시 보냅니다.'),
152
152
  }),
153
153
  ]);
@@ -158,7 +158,7 @@ const settingsPublicSchema = z.strictObject({
158
158
  ambient_mute: z.boolean().describe('세션 중 확인일 알림 문장을 숨길지 정합니다.').optional(),
159
159
  premise_sync: z.boolean().describe('추적 전제를 계정과 동기화할지 명시적으로 선택합니다.').optional(),
160
160
  due_only: z.boolean().describe('동기화할 때 확인일이 된 기록만 가져옵니다.').optional(),
161
- import_settlements: z.boolean().describe('웹에서 기록한 실제 결과를 로컬 원장에 반영합니다.').optional(),
161
+ import_settlements: z.boolean().describe('웹에서 기록한 실제 결과를 로컬 판단 기록에 반영합니다.').optional(),
162
162
  push_local: z.boolean().describe('계정에 닿지 못한 로컬 변경을 다시 보냅니다.').optional(),
163
163
  }).superRefine((value, ctx) => {
164
164
  const parsed = settingsSchema.safeParse(value);
@@ -281,8 +281,11 @@ export const decide = {
281
281
  if (result.isError || !Array.isArray(a['premises']))
282
282
  return result;
283
283
  const sc = result.structuredContent;
284
- if (sc?.['over_fire_gate'] && sc['over_fire_gate']['fired'] !== true)
285
- return result;
284
+ // Record the user's premises REGARDLESS of the over-fire gate. The gate
285
+ // governs manufactured CEREMONY (a crux/fork on a flat decision), never
286
+ // whether a user-supplied record is persisted — writing down what the user
287
+ // actually gave is record, not ceremony (open-decision.ts: 기록과 의식을
288
+ // 분리한다). Gating this silently dropped premises on low-stakes opens.
286
289
  const premiseResult = await premises.handler({
287
290
  argus_dir: a['argus_dir'],
288
291
  id: a['id'],
@@ -314,8 +317,11 @@ export const decide = {
314
317
  if (action === 'keep_question_open') {
315
318
  return runPublic('argus_capture', { ...a, op: 'still_open', reponder_cadence_days: a['reconsider_cadence_days'] }, premises.handler);
316
319
  }
320
+ // recheck internally REQUIRES a source; the public update_fact schema never
321
+ // surfaced it, so every real call died with a baffling "source: 값을 확인해
322
+ // 주세요". Default to user_stated — the user telling us what they verified.
317
323
  if (action === 'update_fact')
318
- return runPublic('argus_capture', a, recheck.handler);
324
+ return runPublic('argus_capture', { ...a, source: a['source'] ?? 'user_stated' }, recheck.handler);
319
325
  if (action === 'change_prediction')
320
326
  return runPublic('argus_capture', a, amend.handler);
321
327
  return runPublic('argus_capture', a, dismiss.handler);
@@ -219,7 +219,7 @@ export const semanticRecord = {
219
219
  recovery: 'Read the record, then use the active ids and an explicit user authorization. Nothing was written.',
220
220
  });
221
221
  }
222
- return toolError({ ok: false, tool: 'argus_record', error_code: 'INTERNAL_ERROR', message: String(error), recovery: 'Nothing was intentionally written; retry after checking the local ledger.' });
222
+ return toolError({ ok: false, tool: 'argus_record', error_code: 'INTERNAL_ERROR', message: String(error), recovery: 'Nothing was intentionally written; retry after checking the local decision record.' });
223
223
  }
224
224
  },
225
225
  };
@@ -87,11 +87,14 @@ export const settle = {
87
87
  // the decision off check_in forever, while the surface lied "what actually
88
88
  // happened". Instead: DEFER — re-arm with a new check-by so it comes back.
89
89
  if (outcome === 'still_pending') {
90
+ // 조기 defer 허용은 상태기계의 defer-lives-in-due 설계(goalpost 방지)와
91
+ // 충돌한다 — 1.4.x에서 스파인 검토 후에만. 지금은 문구가 정직하게:
92
+ // 확인일이 되면 still_pending+defer_to로 연기할 수 있다고 안내한다.
90
93
  if (checkBy && checkBy > today) {
91
94
  return toolError({
92
95
  ok: false, tool: 'argus_settle', error_code: 'PREMATURE_SETTLE',
93
96
  message: `Not due yet (check-by ${checkBy}, today ${today}).`,
94
- recovery: 'Wait for the check-by date, or amend the date if the timeline changed.',
97
+ recovery: `Nothing to record before the check-by date. On ${checkBy}, if reality still has no answer, call again with outcome="still_pending" and defer_to to pick a new date.`,
95
98
  });
96
99
  }
97
100
  return await deferStillPending({ dir, id, today, now, locale, T, current, whatHappened: a['what_happened'], deferTo: a['defer_to'] });
@@ -26,7 +26,7 @@ const inputSchema = z.strictObject({
26
26
  argus_dir: zArgusDir,
27
27
  due_only: z.boolean().default(false).describe('List only receipts whose check-by date has arrived.'),
28
28
  limit: z.number().int().min(1).max(MAX_LIMIT).default(DEFAULT_LIMIT).describe(`Max receipts to list (default ${DEFAULT_LIMIT}). Due items are ordered first.`),
29
- import_settlements: z.boolean().default(false).describe('Mirror settlements the user already recorded on the WEB into this local ledger (their own outcome + words, verbatim — never an inferred outcome). Fixes the local record still listing a web-settled judgment as due. Only affects terminal-sealed (mcp_) judgments that are sealed locally but settled in the account.'),
29
+ import_settlements: z.boolean().default(false).describe('Mirror settlements the user already recorded on the WEB into this local decision record (their own outcome + words, verbatim — never an inferred outcome). Fixes the local record still listing a web-settled judgment as due. Only affects terminal-sealed (mcp_) judgments that are sealed locally but settled in the account.'),
30
30
  push_local: z.boolean().default(true).describe('Send local changes the account never received — a settle, a dismiss, or a moved check-by whose one push failed (offline, or the token was added later). Default true: without it the account keeps listing a closed decision as due and the Companion Brief keeps emailing it. Pass false to inspect the account without writing to it.'),
31
31
  });
32
32
  /**
@@ -51,7 +51,7 @@ const KO_FIELD_DESCRIPTIONS = {
51
51
  load_bearing: '틀리면 결정이 바뀌는 핵심 전제인지 표시합니다.',
52
52
  recheck_cadence_days: '전제 사실을 다시 확인할 간격(일)입니다.',
53
53
  reponder_cadence_days: '미결 질문을 다시 볼 간격(일)입니다.',
54
- reconsider_cadence_days: 'reponder_cadence_days의 호환 별칭입니다.',
54
+ reconsider_cadence_days: '미결 질문을 다시 살펴볼 간격(일)입니다.',
55
55
  predicate: '현실이 참/거짓으로 확인할 수 있는 예측입니다.',
56
56
  check_by: '예측을 현실과 대조할 미래 확인일(YYYY-MM-DD)입니다.',
57
57
  predicate_owner: '예측 문장의 작성 주체입니다. 출처를 꾸미지 않습니다.',
@@ -77,7 +77,7 @@ const KO_FIELD_DESCRIPTIONS = {
77
77
  view: '불러올 기록의 종류입니다.',
78
78
  due_only: '확인일이 된 기록만 표시합니다.',
79
79
  limit: '표시할 최대 기록 수입니다.',
80
- import_settlements: '웹에서 기록한 실제 결과를 로컬 원장으로 가져옵니다.',
80
+ import_settlements: '웹에서 기록한 실제 결과를 로컬 판단 기록으로 가져옵니다.',
81
81
  push_local: '계정에 닿지 못한 로컬 변경을 다시 보냅니다.',
82
82
  dismiss_reason: '결정을 더는 추적하지 않는 이유입니다.',
83
83
  candidate_id: '포착된 결정 후보 id입니다.',
@@ -153,7 +153,7 @@ const EN_FIELD_DESCRIPTIONS = {
153
153
  ambient_mute: 'Hide due reminder lines during the session.',
154
154
  premise_sync: 'When explicitly enabled, sync tracked premises with the account.',
155
155
  due_only: 'Sync only records whose check date has arrived.',
156
- import_settlements: 'Import results recorded on the web into the local ledger.',
156
+ import_settlements: 'Import results recorded on the web into the local decision record.',
157
157
  push_local: 'Retry local changes that have not reached the account.',
158
158
  };
159
159
  /** JSON Schema for tools/list, generated from the Zod source (drop $schema noise). */
@@ -98,7 +98,7 @@ export function renderLogbook(brief, repositoryId) {
98
98
  if (brief.skipped_unknown > 0)
99
99
  honesty.push(`미지 이벤트 ${brief.skipped_unknown}건 (신버전 기록, 데이터는 안전)`);
100
100
  if (honesty.length > 0) {
101
- out.push('## 원장 상태');
101
+ out.push('## 기록 상태');
102
102
  out.push('');
103
103
  for (const h of honesty)
104
104
  out.push(`- ${h}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argus-decision-mcp",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Argus decision-accountability MCP server — clarify a decision, save a falsifiable prediction, and record what reality did. The model never grades you.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",