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,13 +40,22 @@
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
 
39
56
  import { loadEffectiveConfig, resolveProjectRoot } from './config-loader.ts'
40
57
  import { decideApproval, isDestructiveIterateTool } from './approval-gate.ts'
58
+ import { sessionCwdOf } from './working-directory.ts'
41
59
  import type { Context } from '@deepseek-ai/cordis'
42
60
  import type { ToolErrorInfo, ToolExecution, PreToolDecision } from '@deepseek-ai/dsh-tools'
43
61
 
@@ -81,8 +99,14 @@ export function gateDecision(exec: ToolExecution): PreToolDecision {
81
99
  return { kind: 'cancel' }
82
100
  }
83
101
 
84
- // Resolve the project root (use the call's own `path` arg, else the agent's
85
- // session cwd) to read the effective observatory policy.
102
+ // Resolve the project root whose observatory policy governs this call.
103
+ // SECURITY: the policy source must NOT be model-controlled. `path` is a
104
+ // model argument, so reading the policy from the path-resolved root let a
105
+ // call point `path:` at a directory with `approval: allow` (self-grant) or
106
+ // dodge a human's `deny` in the session workspace. When the session cwd is
107
+ // known, the SESSION workspace's config is authoritative and `path` is
108
+ // ignored; the path-resolved root is only the fallback when there is no
109
+ // session cwd at all (headless/tests). The `ask` default stays fail-closed.
86
110
  let argPath: string | undefined
87
111
  let sessionCwd: string | undefined
88
112
  try {
@@ -91,21 +115,33 @@ export function gateDecision(exec: ToolExecution): PreToolDecision {
91
115
  const p = (args as Record<string, unknown>).path
92
116
  if (typeof p === 'string') argPath = p
93
117
  }
94
- sessionCwd = exec?.agent?.session?.header?.cwd
118
+ // Effective session directory (see ./working-directory.ts): a session whose
119
+ // directory was switched via dsh 0.2.1-alpha.2's `working_directory` tool
120
+ // must have its POLICY read from the workspace it is now in — `header.cwd`
121
+ // alone would read the original project's approval policy.
122
+ sessionCwd = sessionCwdOf(exec)
95
123
  } catch {
96
124
  // hostile/proxied exec — fall through with both undefined (defaults to ask)
97
125
  }
98
- const resolved = resolveProjectRoot(argPath, sessionCwd)
126
+ // Passing sessionCwd as BOTH the input and the anchor makes the root the
127
+ // session cwd itself (no server-cwd fallback can sneak in), anchored there.
128
+ const resolved = sessionCwd
129
+ ? resolveProjectRoot(sessionCwd, sessionCwd)
130
+ : resolveProjectRoot(argPath, sessionCwd)
99
131
  let policy: 'ask' | 'deny' | 'allow' = 'ask'
132
+ // Project language for the HUMAN-FACING ask prompt only (see the header's
133
+ // language rule: structured deny/audit text stays English).
134
+ let language: 'zh' | 'en' = 'en'
100
135
  if (resolved.ok) {
101
136
  const { config } = loadEffectiveConfig(resolved.root)
102
137
  const p = config.observatory?.approval
103
138
  if (p === 'deny') policy = 'deny'
104
139
  else if (p === 'allow') policy = 'allow'
105
140
  // anything else (including a corrupt/missing `ask`) → 'ask'
141
+ if (config.language === 'zh') language = 'zh'
106
142
  }
107
143
 
108
- const decision = decideApproval(exec, policy)
144
+ const decision = decideApproval(exec, policy, language)
109
145
  if (decision.kind === 'deny') {
110
146
  return {
111
147
  kind: 'deny',
@@ -114,11 +150,19 @@ export function gateDecision(exec: ToolExecution): PreToolDecision {
114
150
  ...DENY_INFO,
115
151
  // ToolErrorInfo.reason (0.1.6-alpha.1): raw user-facing detail kept in
116
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.
117
155
  reason: decision.reason,
118
156
  },
119
157
  }
120
158
  }
121
- if (decision.kind === 'ask') 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: Context): void {
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') {