iterate-plugin 3.5.7 → 3.7.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 (79) hide show
  1. package/README.md +97 -22
  2. package/README.zh-CN.md +79 -19
  3. package/dist/approval-gate.js +143 -23
  4. package/dist/atomic-fs.js +148 -17
  5. package/dist/config-loader.js +516 -35
  6. package/dist/config-write.js +83 -22
  7. package/dist/evidence.js +141 -87
  8. package/dist/file-lock.js +193 -0
  9. package/dist/index.js +61 -28
  10. package/dist/jobs.js +56 -11
  11. package/dist/live.js +54 -14
  12. package/dist/meta-review.js +132 -28
  13. package/dist/method-scope.js +31 -8
  14. package/dist/review-scope.js +32 -9
  15. package/dist/review.js +96 -17
  16. package/dist/session-hooks.js +61 -14
  17. package/dist/skill-prompt.js +396 -66
  18. package/dist/tools/checkpoint.js +85 -19
  19. package/dist/tools/config.js +44 -2
  20. package/dist/tools/context.js +22 -5
  21. package/dist/tools/decision-log.js +106 -28
  22. package/dist/tools/defense-events.js +90 -31
  23. package/dist/tools/defense-store.js +28 -3
  24. package/dist/tools/experience-bank.js +108 -43
  25. package/dist/tools/experience-store.js +54 -4
  26. package/dist/tools/fix.js +347 -119
  27. package/dist/tools/history.js +68 -14
  28. package/dist/tools/present.js +80 -0
  29. package/dist/tools/prune.js +150 -53
  30. package/dist/tools/quality-gate.js +52 -2
  31. package/dist/tools/quality-store.js +17 -1
  32. package/dist/tools/review.js +158 -7
  33. package/dist/tools/transcript.js +173 -36
  34. package/dist/tools/triage.js +67 -12
  35. package/dist/tools/validate.js +115 -24
  36. package/dist/transcript.js +267 -4
  37. package/dist/working-directory.js +114 -0
  38. package/lib/client.js +1912 -512
  39. package/lib/parse.js +1001 -103
  40. package/package.json +16 -9
  41. package/src/approval-gate.ts +160 -23
  42. package/src/atomic-fs.ts +152 -17
  43. package/src/client/i18n.ts +517 -0
  44. package/src/client/index.ts +1031 -369
  45. package/src/client/session-cache.ts +54 -0
  46. package/src/config-loader.ts +549 -36
  47. package/src/config-write.ts +108 -26
  48. package/src/evidence.ts +194 -92
  49. package/src/file-lock.ts +211 -0
  50. package/src/index.ts +60 -29
  51. package/src/jobs.ts +63 -12
  52. package/src/live.ts +50 -12
  53. package/src/meta-review.ts +151 -34
  54. package/src/method-scope.ts +31 -8
  55. package/src/review-scope.ts +33 -10
  56. package/src/review.ts +96 -17
  57. package/src/session-hooks.ts +60 -13
  58. package/src/skill-prompt.ts +396 -66
  59. package/src/tools/checkpoint.ts +81 -19
  60. package/src/tools/config.ts +44 -2
  61. package/src/tools/context.ts +25 -5
  62. package/src/tools/decision-log.ts +114 -23
  63. package/src/tools/defense-events.ts +87 -31
  64. package/src/tools/defense-store.ts +30 -3
  65. package/src/tools/experience-bank.ts +112 -44
  66. package/src/tools/experience-store.ts +54 -5
  67. package/src/tools/fix.ts +258 -20
  68. package/src/tools/history.ts +74 -15
  69. package/src/tools/present.ts +89 -0
  70. package/src/tools/prune.ts +154 -51
  71. package/src/tools/quality-gate.ts +57 -2
  72. package/src/tools/quality-store.ts +18 -1
  73. package/src/tools/review.ts +155 -6
  74. package/src/tools/transcript.ts +178 -36
  75. package/src/tools/triage.ts +70 -13
  76. package/src/tools/validate.ts +138 -32
  77. package/src/transcript.ts +279 -4
  78. package/src/types.ts +29 -1
  79. package/src/working-directory.ts +126 -0
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Wires the {@link decideApproval} policy gate to dsh's `tools/pre-execute`
5
5
  * waterfall. This is the AUTHORITATIVE approval seam for destructive iterate
6
- * tools (`iterate_fix` / `iterate_rollback` / `iterate_prune` with dryRun:false):
6
+ * tools (`iterate_fix` / `iterate_rollback` / `iterate_prune` with dryRun:false
7
+ * / `iterate_config` with operation:"write"):
7
8
  *
8
9
  * - `allow` policy → the call runs.
9
10
  * - `deny` policy → the call is refused (fail-closed), surfaced as an
@@ -13,11 +14,19 @@
13
14
  * plus the human-readable reason) so durable
14
15
  * projections can route it distinctly from a normal
15
16
  * tool failure.
16
- * - `ask` policy → return `{ kind: 'ask', reason }`; dsh's own
17
- * scheduler routes it through the `approval` service
18
- * (see `@deepseek-ai/dsh-user-approval`), which
19
- * prompts the human and audits an approve/deny pair
20
- * on the session.
17
+ * - `ask` policy → return `{ kind: 'ask', reason, displayReason }`;
18
+ * dsh's own scheduler routes it through the
19
+ * `approval` service (see
20
+ * `@deepseek-ai/dsh-user-approval`), which prompts
21
+ * the human and audits an approve/deny pair on the
22
+ * session. `reason` is the audited English summary;
23
+ * `displayReason` (dsh 0.2.x) is the localized
24
+ * prompt text. LANGUAGE RULE: human-facing text
25
+ * follows the project `language` (zh|en); every
26
+ * structured/durable field (deny reason,
27
+ * `info.reason`, decision-log entries, console
28
+ * logs) stays English so machine matchers never
29
+ * depend on a locale.
21
30
  * - canceled → if the caller aborted the invocation before
22
31
  * dispatch, return `{ kind: 'cancel' }` (the
23
32
  * canonical 0.1.6-alpha.1 cancellation result) so a
@@ -31,12 +40,21 @@
31
40
  *
32
41
  * Safety properties:
33
42
  * - Read-only tools and non-iterate tools are always allowed (the gate only
34
- * inspects the three destructive iterate toolnames).
43
+ * inspects the four gated iterate toolnames, and of those only prune
44
+ * `dryRun:false` and `iterate_config` `operation:"write"` — a config read
45
+ * or a dry-run prune short-circuits to allow inside the policy gate).
46
+ * - The approval policy is read from the SESSION workspace whenever a
47
+ * session cwd is known — the model-controlled `path` argument can never
48
+ * choose where the human's policy comes from (it is only consulted when
49
+ * there is no session cwd at all). Otherwise `path:` could point at an
50
+ * `approval: allow` directory to self-grant, or dodge a `deny` in the
51
+ * workspace.
35
52
  * - If the project root / observatory config cannot be resolved, the policy
36
53
  * degrades to `ask` (fail-safe: destructive writes always require consent).
37
54
  */
38
55
  import { loadEffectiveConfig, resolveProjectRoot } from "./config-loader.js";
39
56
  import { decideApproval, isDestructiveIterateTool } from "./approval-gate.js";
57
+ import { sessionCwdOf } from "./working-directory.js";
40
58
  /** Structured error identity attached to policy denials (dsh 0.1.6-alpha.1:
41
59
  * `deny.info` lets durable projections / retry / replay distinguish an
42
60
  * approval-policy denial from a tool-thrown failure). */
@@ -77,8 +95,14 @@ export function gateDecision(exec) {
77
95
  if (signal && typeof signal.aborted === 'boolean' && signal.aborted) {
78
96
  return { kind: 'cancel' };
79
97
  }
80
- // Resolve the project root (use the call's own `path` arg, else the agent's
81
- // session cwd) to read the effective observatory policy.
98
+ // Resolve the project root whose observatory policy governs this call.
99
+ // SECURITY: the policy source must NOT be model-controlled. `path` is a
100
+ // model argument, so reading the policy from the path-resolved root let a
101
+ // call point `path:` at a directory with `approval: allow` (self-grant) or
102
+ // dodge a human's `deny` in the session workspace. When the session cwd is
103
+ // known, the SESSION workspace's config is authoritative and `path` is
104
+ // ignored; the path-resolved root is only the fallback when there is no
105
+ // session cwd at all (headless/tests). The `ask` default stays fail-closed.
82
106
  let argPath;
83
107
  let sessionCwd;
84
108
  try {
@@ -88,13 +112,24 @@ export function gateDecision(exec) {
88
112
  if (typeof p === 'string')
89
113
  argPath = p;
90
114
  }
91
- sessionCwd = exec?.agent?.session?.header?.cwd;
115
+ // Effective session directory (see ./working-directory.ts): a session whose
116
+ // directory was switched via dsh 0.2.1-alpha.2's `working_directory` tool
117
+ // must have its POLICY read from the workspace it is now in — `header.cwd`
118
+ // alone would read the original project's approval policy.
119
+ sessionCwd = sessionCwdOf(exec);
92
120
  }
93
121
  catch {
94
122
  // hostile/proxied exec — fall through with both undefined (defaults to ask)
95
123
  }
96
- const resolved = resolveProjectRoot(argPath, sessionCwd);
124
+ // Passing sessionCwd as BOTH the input and the anchor makes the root the
125
+ // session cwd itself (no server-cwd fallback can sneak in), anchored there.
126
+ const resolved = sessionCwd
127
+ ? resolveProjectRoot(sessionCwd, sessionCwd)
128
+ : resolveProjectRoot(argPath, sessionCwd);
97
129
  let policy = 'ask';
130
+ // Project language for the HUMAN-FACING ask prompt only (see the header's
131
+ // language rule: structured deny/audit text stays English).
132
+ let language = 'en';
98
133
  if (resolved.ok) {
99
134
  const { config } = loadEffectiveConfig(resolved.root);
100
135
  const p = config.observatory?.approval;
@@ -103,8 +138,10 @@ export function gateDecision(exec) {
103
138
  else if (p === 'allow')
104
139
  policy = 'allow';
105
140
  // anything else (including a corrupt/missing `ask`) → 'ask'
141
+ if (config.language === 'zh')
142
+ language = 'zh';
106
143
  }
107
- const decision = decideApproval(exec, policy);
144
+ const decision = decideApproval(exec, policy, language);
108
145
  if (decision.kind === 'deny') {
109
146
  return {
110
147
  kind: 'deny',
@@ -113,12 +150,19 @@ export function gateDecision(exec) {
113
150
  ...DENY_INFO,
114
151
  // ToolErrorInfo.reason (0.1.6-alpha.1): raw user-facing detail kept in
115
152
  // durable state; the model-facing text still only sees `reason` above.
153
+ // Deliberately the ENGLISH structured reason, never the localized
154
+ // prompt — durable projections must match on a stable string.
116
155
  reason: decision.reason,
117
156
  },
118
157
  };
119
158
  }
120
- if (decision.kind === 'ask')
121
- return { kind: 'ask', reason: decision.reason };
159
+ // dsh 0.2.x: `displayReason` is the localized prompt text (en + the
160
+ // configured locale); `reason` stays the audited English summary. Both are
161
+ // forwarded so the approval UI can show the full localized explanation
162
+ // (including the WARNING line for gate-disabling config writes).
163
+ if (decision.kind === 'ask') {
164
+ return { kind: 'ask', reason: decision.reason, displayReason: decision.displayReason };
165
+ }
122
166
  return { kind: 'allow' };
123
167
  }
124
168
  /**
@@ -139,6 +183,9 @@ export function registerSessionHooks(ctx) {
139
183
  return Promise.resolve({
140
184
  kind: 'ask',
141
185
  reason: 'iterate approval gate unavailable — require consent',
186
+ // Fail-safe path: no config was read, so the localized prompt falls
187
+ // back to English (displayReason requires at least an `en` entry).
188
+ displayReason: { en: 'iterate approval gate unavailable — require consent' },
142
189
  });
143
190
  }
144
191
  if (decision.kind === 'ask') {