one-code-extension 0.1.4 → 0.3.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/LICENSE +20 -0
- package/README.md +200 -172
- package/agents/explore.md +9 -6
- package/agents/general-purpose.md +1 -1
- package/agents/plan.md +6 -4
- package/docs/guide/README.md +52 -0
- package/docs/guide/bring-your-claude-code-setup.md +126 -0
- package/docs/guide/configuration.md +171 -0
- package/docs/guide/differences-from-claude-code.md +150 -0
- package/docs/guide/doctor.md +86 -0
- package/docs/guide/hooks.md +109 -0
- package/docs/guide/installation.md +124 -0
- package/docs/guide/permissions-modes-and-auto-mode.md +450 -0
- package/docs/guide/providers-and-models.md +194 -0
- package/docs/guide/reference.md +224 -0
- package/docs/guide/sessions-and-context.md +190 -0
- package/docs/guide/skills-plugins-and-mcp.md +176 -0
- package/docs/guide/subagents-and-workflows.md +193 -0
- package/docs/guide/tasks-and-background-work.md +111 -0
- package/docs/guide/terminal-interface.md +155 -0
- package/docs/guide/tools.md +175 -0
- package/docs/guide/troubleshooting.md +175 -0
- package/extensions/ask-user/index.ts +65 -71
- package/extensions/ask-user/questions.ts +15 -28
- package/extensions/ask-user/widget.ts +533 -0
- package/extensions/auto-mode/actions.ts +9 -26
- package/extensions/auto-mode/classifier-prompt.ts +127 -0
- package/extensions/auto-mode/classifier.ts +305 -227
- package/extensions/auto-mode/config.ts +279 -77
- package/extensions/auto-mode/defaults.ts +17 -60
- package/extensions/auto-mode/instructions.ts +2 -1
- package/extensions/auto-mode/model-picker.ts +53 -31
- package/extensions/auto-mode/model-select.ts +153 -82
- package/extensions/auto-mode/paths.ts +13 -1
- package/extensions/auto-mode/pause.ts +8 -0
- package/extensions/auto-mode/prompt.ts +205 -238
- package/extensions/auto-mode/recoverability.ts +229 -0
- package/extensions/auto-mode/rules.ts +92 -75
- package/extensions/auto-mode/safety-floor.ts +43 -12
- package/extensions/auto-mode/sensitive.ts +11 -0
- package/extensions/auto-mode/setup-run.ts +245 -0
- package/extensions/auto-mode/setup.ts +235 -0
- package/extensions/auto-mode/shell-analysis.ts +332 -26
- package/extensions/auto-mode/transcript.ts +102 -0
- package/extensions/background/index.ts +256 -43
- package/extensions/background/monitor-batch.ts +58 -0
- package/extensions/background/registry.ts +9 -0
- package/extensions/background/wakeup.ts +45 -0
- package/extensions/bash/background.ts +47 -22
- package/extensions/bash/guards.ts +229 -0
- package/extensions/bash/index.ts +81 -19
- package/extensions/branding/index.ts +44 -18
- package/extensions/branding/startup.ts +23 -8
- package/extensions/claude-compat/index.ts +17 -2
- package/extensions/claude-context/index.ts +41 -12
- package/extensions/clear/index.ts +27 -0
- package/extensions/compaction/fit.ts +121 -0
- package/extensions/compaction/index.ts +176 -71
- package/extensions/compaction/prompt.ts +21 -1
- package/extensions/context-budget/budget.ts +61 -0
- package/extensions/context-budget/index.ts +66 -0
- package/extensions/context-management/index.ts +40 -16
- package/extensions/doctor/build.ts +226 -0
- package/extensions/doctor/cli.ts +99 -0
- package/extensions/doctor/compat.ts +532 -0
- package/extensions/doctor/dependencies.ts +186 -0
- package/extensions/doctor/fix-prompt.ts +125 -0
- package/extensions/doctor/index.ts +308 -0
- package/extensions/doctor/models.ts +231 -0
- package/extensions/doctor/presets.ts +208 -0
- package/extensions/doctor/report.ts +189 -0
- package/extensions/doctor/update-lookup.ts +46 -0
- package/extensions/doctor/viewer.ts +124 -0
- package/extensions/effort/index.ts +41 -4
- package/extensions/effort/slider.ts +33 -2
- package/extensions/file-tracker/index.ts +143 -20
- package/extensions/file-tracker/replay.ts +46 -0
- package/extensions/file-tracker/tracker.ts +51 -4
- package/extensions/footer/footer-line.ts +207 -0
- package/extensions/footer/index.ts +152 -0
- package/extensions/footer/pr.ts +53 -0
- package/extensions/hooks/executor.ts +57 -20
- package/extensions/hooks/formatter-notice.ts +76 -0
- package/extensions/hooks/index.ts +314 -73
- package/extensions/hooks/matcher.ts +35 -1
- package/extensions/hooks/plugin-hooks.ts +3 -3
- package/extensions/hooks/protocol.ts +58 -0
- package/extensions/hooks/subagent-bridge.ts +77 -0
- package/extensions/hooks/trust.ts +15 -4
- package/extensions/init/index.ts +26 -0
- package/extensions/init/prompt.ts +40 -0
- package/extensions/interrupted/index.ts +35 -0
- package/extensions/interrupted/line.ts +9 -0
- package/extensions/lib/abort.ts +15 -0
- package/extensions/lib/agent-loader.ts +183 -0
- package/extensions/lib/anthropic-payload.ts +15 -0
- package/extensions/lib/atomic-write.ts +56 -0
- package/extensions/lib/bridge-watch.ts +20 -0
- package/extensions/lib/cache-retention.ts +60 -0
- package/extensions/lib/capability-index.ts +466 -0
- package/extensions/lib/claude-context.ts +318 -27
- package/extensions/lib/claude-settings.ts +64 -0
- package/extensions/lib/consent-preview.ts +30 -0
- package/extensions/lib/deferred.ts +144 -13
- package/extensions/lib/favorites.ts +43 -0
- package/extensions/lib/fetch-timeout.ts +15 -0
- package/extensions/lib/frontmatter.ts +31 -0
- package/extensions/lib/git.ts +44 -2
- package/extensions/lib/hook-gate.ts +90 -0
- package/extensions/lib/interrupt.ts +94 -0
- package/extensions/lib/mcp-overrides.ts +61 -0
- package/extensions/lib/mcp-share.ts +25 -0
- package/extensions/lib/mcp-status.ts +34 -0
- package/extensions/lib/memory.ts +41 -6
- package/extensions/lib/model-facts.generated.json +2480 -0
- package/extensions/lib/model-facts.ts +162 -0
- package/extensions/lib/model-policy.ts +145 -189
- package/extensions/lib/model-tier.ts +480 -64
- package/extensions/lib/model-unusable.ts +37 -0
- package/extensions/lib/notifications.ts +320 -1
- package/extensions/lib/one-code-settings.ts +63 -0
- package/extensions/lib/original-command.ts +71 -0
- package/extensions/lib/package-version.ts +31 -0
- package/extensions/lib/paths.ts +39 -4
- package/extensions/lib/permission-gate.ts +192 -0
- package/extensions/lib/persisted-output.ts +18 -0
- package/extensions/lib/pi-ai-estimate.ts +160 -0
- package/extensions/lib/pi-version.ts +1 -1
- package/extensions/lib/plan-mode-channels.ts +11 -0
- package/extensions/lib/plugin-overrides.ts +24 -0
- package/extensions/lib/plugin-root.ts +44 -0
- package/extensions/lib/plugins.ts +233 -54
- package/extensions/lib/prefix-warm-gate.ts +157 -0
- package/extensions/lib/process-tree.ts +56 -0
- package/extensions/lib/reminders.ts +321 -68
- package/extensions/lib/scratchpad.ts +11 -8
- package/extensions/lib/session-lifecycle.ts +28 -0
- package/extensions/lib/settings-channels.ts +14 -0
- package/extensions/lib/shell-tasks.ts +54 -0
- package/extensions/lib/skill-overrides.ts +86 -0
- package/extensions/lib/skill-scan.ts +110 -0
- package/extensions/lib/text-width.ts +267 -0
- package/extensions/lib/tui-render.ts +281 -54
- package/extensions/lib/usage-bus.ts +75 -0
- package/extensions/lib/usage-tracker.ts +67 -0
- package/extensions/lib/worktree-isolation.ts +170 -0
- package/extensions/lsp/client.ts +69 -12
- package/extensions/lsp/format.ts +12 -2
- package/extensions/lsp/index.ts +273 -84
- package/extensions/lsp/install-hints.ts +43 -0
- package/extensions/lsp/keep-alive.ts +19 -0
- package/extensions/lsp/plugin-servers.ts +331 -0
- package/extensions/lsp/watcher.ts +157 -0
- package/extensions/mcp/announce.ts +99 -0
- package/extensions/mcp/client.ts +181 -22
- package/extensions/mcp/config.ts +55 -24
- package/extensions/mcp/index.ts +586 -86
- package/extensions/mcp/oauth/browser.ts +26 -0
- package/extensions/mcp/oauth/callback.ts +104 -0
- package/extensions/mcp/oauth/flow.ts +68 -0
- package/extensions/mcp/oauth/provider.ts +147 -0
- package/extensions/mcp/oauth/store.ts +86 -0
- package/extensions/mcp/panel/keys.ts +34 -0
- package/extensions/mcp/panel/model.ts +62 -0
- package/extensions/mcp/panel/render.ts +141 -0
- package/extensions/mcp/panel/state.ts +108 -0
- package/extensions/mcp/schema.ts +83 -6
- package/extensions/mcp/trust.ts +340 -0
- package/extensions/memory/entries.ts +163 -0
- package/extensions/memory/index.ts +119 -2
- package/extensions/memory/open-external.ts +97 -0
- package/extensions/memory/panel.ts +124 -0
- package/extensions/model-default/index.ts +74 -0
- package/extensions/notebook/index.ts +36 -18
- package/extensions/notebook/notebook.ts +110 -17
- package/extensions/permissions/index.ts +1012 -247
- package/extensions/permissions/matcher.ts +488 -57
- package/extensions/permissions/modes.ts +55 -7
- package/extensions/permissions/project-trust.ts +81 -0
- package/extensions/permissions/protected-paths.ts +16 -3
- package/extensions/permissions/rule-prose.ts +79 -0
- package/extensions/permissions/session-grant.ts +91 -0
- package/extensions/permissions/settings.ts +112 -33
- package/extensions/permissions/subagent-gate.ts +57 -0
- package/extensions/plan-mode/index.ts +143 -33
- package/extensions/plan-mode/reminder.ts +16 -19
- package/extensions/plan-mode/viewer.ts +40 -31
- package/extensions/plugins/counts.ts +68 -0
- package/extensions/plugins/index.ts +496 -32
- package/extensions/plugins/install/install.ts +167 -0
- package/extensions/plugins/install/paths.ts +23 -0
- package/extensions/plugins/install/registry.ts +72 -0
- package/extensions/plugins/marketplace/git.ts +103 -0
- package/extensions/plugins/marketplace/official.ts +36 -0
- package/extensions/plugins/marketplace/parse.ts +94 -0
- package/extensions/plugins/marketplace/registry.ts +60 -0
- package/extensions/plugins/marketplace/sync.ts +146 -0
- package/extensions/plugins/marketplace/types.ts +128 -0
- package/extensions/plugins/panel/keys.ts +52 -0
- package/extensions/plugins/panel/render.ts +315 -0
- package/extensions/plugins/panel/rows.ts +230 -0
- package/extensions/plugins/panel/state.ts +333 -0
- package/extensions/recap/index.ts +215 -0
- package/extensions/recap/prompt.ts +44 -0
- package/extensions/recap/scheduler.ts +87 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/search-tools/tools.ts +7 -5
- package/extensions/skill/index.ts +360 -34
- package/extensions/skill/invoke.ts +185 -0
- package/extensions/skill/listing.ts +21 -0
- package/extensions/skill/panel/keys.ts +41 -0
- package/extensions/skill/panel/render.ts +91 -0
- package/extensions/skill/panel/state.ts +115 -0
- package/extensions/spinner/index.ts +110 -0
- package/extensions/spinner/line.ts +51 -0
- package/extensions/spinner/verbs.ts +201 -0
- package/extensions/subagents/agents.ts +88 -44
- package/extensions/subagents/default-model.ts +62 -32
- package/extensions/subagents/delegation-steer.ts +23 -0
- package/extensions/subagents/hand-back-review.ts +67 -0
- package/extensions/subagents/index.ts +1545 -346
- package/extensions/subagents/live-runs.ts +293 -0
- package/extensions/subagents/model-select.ts +252 -82
- package/extensions/subagents/outcome.ts +90 -0
- package/extensions/subagents/panel-keys.ts +41 -0
- package/extensions/subagents/panel-render.ts +357 -0
- package/extensions/subagents/panel-widget.ts +330 -0
- package/extensions/subagents/pending-claim.ts +45 -0
- package/extensions/subagents/prose.ts +105 -0
- package/extensions/subagents/runner.ts +674 -0
- package/extensions/subagents/runs.ts +68 -1
- package/extensions/subagents/send-to-main-tool.ts +55 -0
- package/extensions/subagents/session-turns.ts +138 -0
- package/extensions/subagents/shell-panel.ts +198 -0
- package/extensions/subagents/usage.ts +16 -4
- package/extensions/subagents/worktree.ts +13 -0
- package/extensions/system-prompt/environment.ts +5 -3
- package/extensions/system-prompt/git-status.ts +93 -0
- package/extensions/system-prompt/index.ts +23 -1
- package/extensions/system-prompt/template.ts +34 -4
- package/extensions/system-prompt/tiers/common.ts +31 -6
- package/extensions/system-prompt/tiers/low.ts +13 -3
- package/extensions/system-prompt/tiers/mid.ts +21 -8
- package/extensions/system-reminder/index.ts +53 -9
- package/extensions/tasks/index.ts +65 -16
- package/extensions/tasks/store.ts +76 -3
- package/extensions/tool-search/announce.ts +42 -0
- package/extensions/tool-search/index.ts +136 -13
- package/extensions/turn-duration/index.ts +60 -0
- package/extensions/turn-duration/line.ts +22 -0
- package/extensions/turn-duration/span.ts +41 -0
- package/extensions/turn-duration/verbs.ts +24 -0
- package/extensions/web/backends.ts +421 -0
- package/extensions/web/index.ts +178 -21
- package/extensions/web-fetch/index.ts +74 -18
- package/extensions/web-fetch/summarize.ts +1 -1
- package/extensions/workflow/agent-session.ts +182 -112
- package/extensions/workflow/globals.ts +129 -106
- package/extensions/workflow/index.ts +343 -28
- package/extensions/workflow/records.ts +118 -0
- package/extensions/workflow/run-manager.ts +80 -31
- package/extensions/workflow/saved-workflows.ts +2 -1
- package/extensions/workflow/script-worker.mjs +110 -0
- package/extensions/workflow/types.ts +74 -8
- package/extensions/workflow/viewer.ts +562 -0
- package/extensions/workflow/vm-runtime.ts +118 -22
- package/extensions/workflow/widget.ts +126 -18
- package/extensions/worktree/guards.ts +240 -0
- package/extensions/worktree/index.ts +30 -5
- package/extensions/worktree/rewrite.ts +32 -16
- package/package.json +20 -10
- package/skills/code-review/SKILL.md +165 -0
- package/skills/fewer-permission-prompts/SKILL.md +61 -0
- package/skills/security-review/SKILL.md +178 -0
- package/skills/simplify/SKILL.md +70 -0
- package/themes/{one-code-light.json → onecode-light.json} +6 -1
- package/themes/{one-code.json → onecode.json} +6 -1
- package/types/app.d.ts +8 -1
- package/types/community-packages.d.ts +45 -2
- package/docs/decisions/auto-mode.md +0 -526
- package/docs/decisions/branding.md +0 -127
- package/docs/decisions/code-review-remediation.md +0 -106
- package/docs/decisions/compaction.md +0 -140
- package/docs/decisions/distribution.md +0 -168
- package/docs/decisions/hooks.md +0 -64
- package/docs/decisions/lsp.md +0 -58
- package/docs/decisions/mcp.md +0 -104
- package/docs/decisions/memory-state.md +0 -170
- package/docs/decisions/model-policy.md +0 -126
- package/docs/decisions/modes.md +0 -178
- package/docs/decisions/skills-plugins.md +0 -56
- package/docs/decisions/subagents-workflows.md +0 -225
- package/docs/decisions/system-prompt.md +0 -98
- package/docs/decisions/tools.md +0 -407
- package/docs/decisions/tui.md +0 -217
- package/docs/decisions.md +0 -148
- package/extensions/subagents/child.ts +0 -414
- package/extensions/subagents/rpc-turns.ts +0 -123
- package/extensions/todo/index.ts +0 -133
- package/extensions/workflow/permission-gate.ts +0 -71
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/auto-mode setup` — the impure half: gathering facts (exec/fs) and the
|
|
3
|
+
* drafting model call. Parsing, prompts, and validation live in setup.ts; UI
|
|
4
|
+
* wiring in extensions/permissions/index.ts.
|
|
5
|
+
*
|
|
6
|
+
* Gathering is best-effort probe-by-probe: any probe that fails lands in
|
|
7
|
+
* `gatherNotes` instead of the draft silently pretending it ran (the drafting
|
|
8
|
+
* prompt's evidence rules key off those notes). The model call mirrors the
|
|
9
|
+
* classifier's fail-loud shape — no reasoning/temperature, hard timeout, and a
|
|
10
|
+
* parse failure throws rather than persisting a half-read draft — but tries
|
|
11
|
+
* the SESSION model first: drafting wants the most capable model available,
|
|
12
|
+
* where the per-call classifier wants small-and-contained.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { execFile } from "node:child_process";
|
|
16
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
19
|
+
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
|
20
|
+
import { claudeUserSettingsPath, readSettingsFile } from "../lib/claude-settings.ts";
|
|
21
|
+
import { withReasoningFallback } from "../lib/model-policy.ts";
|
|
22
|
+
import { oneCodeSettingsPath } from "../lib/one-code-settings.ts";
|
|
23
|
+
import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
24
|
+
import type { AutoModeConfig } from "./config.ts";
|
|
25
|
+
import { classifierCandidates, replyText, withAuthBaseUrl } from "./model-select.ts";
|
|
26
|
+
import { buildSetupPrompt, parseGitRemotes, redactSecrets, type SetupDraft, parseSetupDraft, type SetupFacts } from "./setup.ts";
|
|
27
|
+
import { claudeUserDir } from "../lib/paths.ts";
|
|
28
|
+
|
|
29
|
+
const PROBE_TIMEOUT_MS = 10_000;
|
|
30
|
+
/** Drafting reads a big fact dump and writes a full slot list — give it room. */
|
|
31
|
+
const DRAFT_TIMEOUT_MS = 120_000;
|
|
32
|
+
const DRAFT_MAX_TOKENS = 8192;
|
|
33
|
+
const CLAUDE_MD_LIMIT = 4_000;
|
|
34
|
+
const HISTORY_LINES = 200;
|
|
35
|
+
|
|
36
|
+
/** One probe: run a command, return trimmed stdout, or undefined on any failure. */
|
|
37
|
+
function probe(cmd: string, args: string[], cwd: string, notes: string[], what: string): Promise<string | undefined> {
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
execFile(cmd, args, { cwd, timeout: PROBE_TIMEOUT_MS, maxBuffer: 1024 * 1024 }, (error, stdout) => {
|
|
40
|
+
if (error) {
|
|
41
|
+
notes.push(`${what} was not gathered (${cmd} failed: ${error.message.split("\n")[0]})`);
|
|
42
|
+
resolve(undefined);
|
|
43
|
+
} else {
|
|
44
|
+
resolve(stdout.trim());
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Read a file if it exists, truncated to `limit`. A file that exists but
|
|
52
|
+
* cannot be read is NOT the same as no file: it lands in `notes` so the
|
|
53
|
+
* drafting prompt's evidence rules see "present but unreadable" rather than
|
|
54
|
+
* silently treating it as absent.
|
|
55
|
+
*/
|
|
56
|
+
const readIfPresent = (path: string, limit: number, notes: string[], what: string): string | undefined => {
|
|
57
|
+
try {
|
|
58
|
+
if (!existsSync(path)) return undefined;
|
|
59
|
+
const text = readFileSync(path, "utf-8");
|
|
60
|
+
return text.length > limit ? `${text.slice(0, limit)}\n…[truncated]` : text;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
notes.push(`${what} exists but could not be read (${(error as Error).message.split("\n")[0]})`);
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export interface GatherOptions {
|
|
68
|
+
cwd: string;
|
|
69
|
+
home: string;
|
|
70
|
+
username: string;
|
|
71
|
+
/** The user's answer to the usage question, verbatim. */
|
|
72
|
+
usage: string;
|
|
73
|
+
includeShellHistory: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function gatherFacts(options: GatherOptions): Promise<SetupFacts> {
|
|
77
|
+
const { cwd, home } = options;
|
|
78
|
+
const notes: string[] = [];
|
|
79
|
+
|
|
80
|
+
const gitRoot = await probe("git", ["rev-parse", "--show-toplevel"], cwd, notes, "git root");
|
|
81
|
+
// The three follow-ups depend only on gitRoot, not on each other — run them
|
|
82
|
+
// concurrently so a slow `gh` (network) does not stack on the local git calls.
|
|
83
|
+
const [remotesRaw, branch, ghRaw] = gitRoot
|
|
84
|
+
? await Promise.all([
|
|
85
|
+
probe("git", ["remote", "-v"], cwd, notes, "git remotes"),
|
|
86
|
+
probe("git", ["branch", "--show-current"], cwd, notes, "current branch"),
|
|
87
|
+
// gh gives visibility/default branch for the repo the cwd is in; treat
|
|
88
|
+
// its absence as "unknown", never as "private".
|
|
89
|
+
probe("gh", ["repo", "view", "--json", "visibility,nameWithOwner,defaultBranchRef"], cwd, notes, "repository visibility (gh)"),
|
|
90
|
+
])
|
|
91
|
+
: [undefined, undefined, undefined];
|
|
92
|
+
|
|
93
|
+
let repoVisibility: string | undefined;
|
|
94
|
+
let repoNameWithOwner: string | undefined;
|
|
95
|
+
let defaultBranch: string | undefined;
|
|
96
|
+
if (ghRaw) {
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(ghRaw) as {
|
|
99
|
+
visibility?: string;
|
|
100
|
+
nameWithOwner?: string;
|
|
101
|
+
defaultBranchRef?: { name?: string };
|
|
102
|
+
};
|
|
103
|
+
repoVisibility = parsed.visibility;
|
|
104
|
+
repoNameWithOwner = parsed.nameWithOwner;
|
|
105
|
+
defaultBranch = parsed.defaultBranchRef?.name;
|
|
106
|
+
} catch {
|
|
107
|
+
notes.push("gh repo view returned unparseable JSON — repository visibility unknown");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let shellHistory: string[] | undefined;
|
|
112
|
+
if (options.includeShellHistory) {
|
|
113
|
+
const histFile = [process.env.HISTFILE, join(home, ".zsh_history"), join(home, ".bash_history")]
|
|
114
|
+
.filter((path): path is string => !!path)
|
|
115
|
+
.find((path) => existsSync(path));
|
|
116
|
+
if (histFile) {
|
|
117
|
+
const raw = readIfPresent(histFile, 1024 * 1024, notes, "shell history") ?? "";
|
|
118
|
+
shellHistory = raw
|
|
119
|
+
.split("\n")
|
|
120
|
+
.slice(-HISTORY_LINES)
|
|
121
|
+
// zsh extended history stamps "": 1699999999:0;cmd" — keep the command half.
|
|
122
|
+
.map((line) => redactSecrets(line.replace(/^:\s*\d+:\d+;/, "").trim()))
|
|
123
|
+
.filter(Boolean);
|
|
124
|
+
} else {
|
|
125
|
+
notes.push("no shell history file found — usage patterns not gathered");
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
notes.push("shell history scan declined by the user");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// User-scope permissions.allow — audit input, and evidence for carve-outs. Both
|
|
132
|
+
// the borrowed `.claude` file (read-only) and One Code's own file are gathered:
|
|
133
|
+
// a broad rule in either bypasses the classifier, and the audit later splits
|
|
134
|
+
// them (One Code's are removable; Claude Code's it can only warn about).
|
|
135
|
+
const claudeUserPath = claudeUserSettingsPath(home);
|
|
136
|
+
const permissionsAllow: string[] = [];
|
|
137
|
+
for (const path of [claudeUserPath, oneCodeSettingsPath(home)]) {
|
|
138
|
+
const file = readSettingsFile(path) as { permissions?: { allow?: unknown } } | undefined;
|
|
139
|
+
// Present-but-unreadable is not the same as absent (readIfPresent's rule):
|
|
140
|
+
// a malformed `.claude` file must land in notes so the drafting evidence
|
|
141
|
+
// sees "present but unreadable", not silently "no broad rules here".
|
|
142
|
+
if (file === undefined && path === claudeUserPath && existsSync(path)) {
|
|
143
|
+
notes.push("user settings permissions.allow could not be read");
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const allow = file?.permissions?.allow;
|
|
147
|
+
if (Array.isArray(allow)) permissionsAllow.push(...allow.filter((entry): entry is string => typeof entry === "string"));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
cwd,
|
|
152
|
+
username: options.username,
|
|
153
|
+
usage: options.usage,
|
|
154
|
+
gitRoot: gitRoot || undefined,
|
|
155
|
+
remotes: remotesRaw ? parseGitRemotes(remotesRaw) : [],
|
|
156
|
+
currentBranch: branch || undefined,
|
|
157
|
+
repoVisibility,
|
|
158
|
+
repoNameWithOwner,
|
|
159
|
+
defaultBranch,
|
|
160
|
+
claudeMdProject: readIfPresent(join(gitRoot || cwd, "CLAUDE.md"), CLAUDE_MD_LIMIT, notes, "project CLAUDE.md"),
|
|
161
|
+
claudeMdGlobal: readIfPresent(join(claudeUserDir(home), "CLAUDE.md"), CLAUDE_MD_LIMIT, notes, "global CLAUDE.md"),
|
|
162
|
+
shellHistory,
|
|
163
|
+
permissionsAllow,
|
|
164
|
+
gatherNotes: notes,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface DraftDeps {
|
|
169
|
+
registry: ModelRegistry;
|
|
170
|
+
sessionModel: Model<Api> | undefined;
|
|
171
|
+
config: AutoModeConfig;
|
|
172
|
+
defaultEnvironment: string[];
|
|
173
|
+
signal?: AbortSignal;
|
|
174
|
+
/** Surfaces candidate-chain warnings (a stale or cross-provider classifierModel) to the user. */
|
|
175
|
+
onNotice?: (message: string, level: "info" | "warning") => void;
|
|
176
|
+
/** Reports the drafting call's usage to the all-in footer cost. */
|
|
177
|
+
onUsage?: (usage: unknown) => void;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Draft the setup with the most capable model reachable: the session model
|
|
182
|
+
* first, then the classifier candidate chain. Throws on total failure or an
|
|
183
|
+
* unusable draft — the wizard has a user in front of it; nothing here may
|
|
184
|
+
* persist silently wrong.
|
|
185
|
+
*/
|
|
186
|
+
export async function draftSetup(facts: SetupFacts, deps: DraftDeps): Promise<SetupDraft> {
|
|
187
|
+
const built = classifierCandidates({
|
|
188
|
+
available: deps.registry.getAvailable(),
|
|
189
|
+
sessionModel: deps.sessionModel,
|
|
190
|
+
configured: deps.config.classifierModel,
|
|
191
|
+
configuredSetForContainment: deps.config.classifierModelSetFor,
|
|
192
|
+
});
|
|
193
|
+
// The same warnings runClassifier surfaces during real gating (a stale or
|
|
194
|
+
// cross-provider classifierModel) — setup is exactly where the user can fix
|
|
195
|
+
// them, so they must not be dropped here.
|
|
196
|
+
for (const notice of built.notices) deps.onNotice?.(notice.text, notice.level);
|
|
197
|
+
const chain = built.candidates.map((entry) => entry.model);
|
|
198
|
+
const models: Model<Api>[] = [];
|
|
199
|
+
for (const model of deps.sessionModel ? [deps.sessionModel, ...chain] : chain) {
|
|
200
|
+
if (!models.some((seen) => seen.provider === model.provider && seen.id === model.id)) models.push(model);
|
|
201
|
+
}
|
|
202
|
+
if (models.length === 0) throw new Error("no model is available to draft the setup");
|
|
203
|
+
|
|
204
|
+
const { system, user } = buildSetupPrompt(facts, deps.defaultEnvironment);
|
|
205
|
+
let lastError = "";
|
|
206
|
+
for (const model of models) {
|
|
207
|
+
const auth = await deps.registry.getApiKeyAndHeaders(model);
|
|
208
|
+
if (!auth.ok) {
|
|
209
|
+
lastError = auth.error;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const resolved = withAuthBaseUrl(model, auth);
|
|
213
|
+
// Thinking stays off unless the model cannot disable it (withReasoningFallback
|
|
214
|
+
// sends a level up front for catalog-marked models and retries on the 400 for
|
|
215
|
+
// the rest). A rare one-off, so no cross-run memo.
|
|
216
|
+
const reply = await withReasoningFallback(model, (reasoning) => {
|
|
217
|
+
const timeout = AbortSignal.timeout(DRAFT_TIMEOUT_MS);
|
|
218
|
+
return completeSimple(
|
|
219
|
+
resolved,
|
|
220
|
+
{ systemPrompt: system, messages: [{ role: "user", content: user, timestamp: Date.now() }] },
|
|
221
|
+
{
|
|
222
|
+
apiKey: auth.apiKey,
|
|
223
|
+
headers: auth.headers,
|
|
224
|
+
env: auth.env,
|
|
225
|
+
signal: deps.signal ? AbortSignal.any([deps.signal, timeout]) : timeout,
|
|
226
|
+
maxTokens: DRAFT_MAX_TOKENS,
|
|
227
|
+
...(reasoning ? { reasoning } : {}),
|
|
228
|
+
},
|
|
229
|
+
);
|
|
230
|
+
}, undefined, deps.onUsage);
|
|
231
|
+
if (reply.stopReason === "error" || reply.stopReason === "aborted") {
|
|
232
|
+
if (deps.signal?.aborted) throw new Error("setup drafting was cancelled");
|
|
233
|
+
lastError = reply.errorMessage ?? reply.stopReason;
|
|
234
|
+
continue; // a dead model steps to the next candidate; a bad draft does not
|
|
235
|
+
}
|
|
236
|
+
if (reply.stopReason === "length") {
|
|
237
|
+
lastError = "draft truncated at maxTokens";
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
// Parse errors are NOT stepped past: the model responded, its output is
|
|
241
|
+
// unusable, and retrying a different model would hide a prompt bug.
|
|
242
|
+
return parseSetupDraft(replyText(reply), deps.defaultEnvironment);
|
|
243
|
+
}
|
|
244
|
+
throw new Error(`no model could draft the setup (last error: ${lastError})`);
|
|
245
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/auto-mode setup` — the pure half (Claude Code's `/auto-mode-setup` parity).
|
|
3
|
+
*
|
|
4
|
+
* CC 2.1.233 ships a setup wizard: it scans the project, recent usage, and
|
|
5
|
+
* optionally shell history, drafts an auto-mode config (Environment slots,
|
|
6
|
+
* allow carve-outs, extra soft/hard blocks), shows it for review, and writes
|
|
7
|
+
* the `autoMode` object to user settings. It also audits `permissions.allow`
|
|
8
|
+
* for entries broad enough to bypass the classifier ("rules that skip
|
|
9
|
+
* checks"). This module holds everything unit-testable about ours: fact
|
|
10
|
+
* parsing, the drafting prompt, strict draft parsing, proposal rendering, and
|
|
11
|
+
* the allow-rule audit. Gathering (exec/fs) and the model call live in
|
|
12
|
+
* setup-run.ts; UI wiring in extensions/permissions/index.ts.
|
|
13
|
+
*
|
|
14
|
+
* Evidence discipline (CC's, kept deliberately): the draft may only assert
|
|
15
|
+
* what the gathered facts show — everything else stays at its shipped
|
|
16
|
+
* default ("None configured" / the conservative heuristic). A wizard that
|
|
17
|
+
* guesses trust boundaries manufactures authorization; one that leaves gaps
|
|
18
|
+
* merely classifies a little more often.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { isBroadExecutionRule, parseRule } from "../permissions/matcher.ts";
|
|
22
|
+
import { slotName } from "./defaults.ts";
|
|
23
|
+
|
|
24
|
+
export interface SetupFacts {
|
|
25
|
+
cwd: string;
|
|
26
|
+
username: string;
|
|
27
|
+
/** The user's own answer to "how do you use this project" — verbatim. */
|
|
28
|
+
usage: string;
|
|
29
|
+
gitRoot?: string;
|
|
30
|
+
remotes: { name: string; url: string }[];
|
|
31
|
+
currentBranch?: string;
|
|
32
|
+
/** From `gh repo view` — "PUBLIC"/"PRIVATE"/"INTERNAL", or undefined if unknown. */
|
|
33
|
+
repoVisibility?: string;
|
|
34
|
+
repoNameWithOwner?: string;
|
|
35
|
+
defaultBranch?: string;
|
|
36
|
+
/** Truncated CLAUDE.md contents, project and global. */
|
|
37
|
+
claudeMdProject?: string;
|
|
38
|
+
claudeMdGlobal?: string;
|
|
39
|
+
/** Recent shell-history lines (opt-in), secrets already redacted. */
|
|
40
|
+
shellHistory?: string[];
|
|
41
|
+
/** User-scope permissions.allow rules — audit input and carve-out evidence. */
|
|
42
|
+
permissionsAllow: string[];
|
|
43
|
+
/** What could NOT be gathered, so the draft cannot pretend it was. */
|
|
44
|
+
gatherNotes: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** What the drafting model must return; also the shape persisted to settings. */
|
|
48
|
+
export interface SetupDraft {
|
|
49
|
+
environment: string[];
|
|
50
|
+
allow: string[];
|
|
51
|
+
soft_deny: string[];
|
|
52
|
+
hard_deny: string[];
|
|
53
|
+
notes: string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** `git remote -v` output → unique {name, url} pairs (fetch/push collapsed). */
|
|
57
|
+
export function parseGitRemotes(output: string): { name: string; url: string }[] {
|
|
58
|
+
const seen = new Map<string, string>();
|
|
59
|
+
for (const line of output.split("\n")) {
|
|
60
|
+
const match = line.match(/^(\S+)\s+(\S+)\s+\((?:fetch|push)\)$/);
|
|
61
|
+
if (match && !seen.has(match[1])) seen.set(match[1], match[2]);
|
|
62
|
+
}
|
|
63
|
+
return [...seen.entries()].map(([name, url]) => ({ name, url }));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Redact obvious secret material before shell history leaves the machine. */
|
|
67
|
+
export function redactSecrets(line: string): string {
|
|
68
|
+
return line
|
|
69
|
+
.replace(/\b([A-Za-z_]*(?:key|token|secret|password|passwd|credential)[A-Za-z_]*)=\S+/gi, "$1=[redacted]")
|
|
70
|
+
.replace(/(--?[A-Za-z-]*(?:key|token|secret|password|passwd|credential)[A-Za-z-]*)\s+\S+/gi, "$1 [redacted]")
|
|
71
|
+
.replace(/\b(sk-|ghp_|gho_|github_pat_|xox[bap]-|AKIA)[A-Za-z0-9_-]+/g, "[redacted]");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The drafting instruction. The model sees the gathered facts as JSON and the
|
|
76
|
+
* current default Environment slots, and must return STRICT JSON — nothing
|
|
77
|
+
* else — so the caller can parse mechanically and fail loud on anything off.
|
|
78
|
+
*/
|
|
79
|
+
export function buildSetupPrompt(
|
|
80
|
+
facts: SetupFacts,
|
|
81
|
+
defaultEnvironment: string[],
|
|
82
|
+
): { system: string; user: string } {
|
|
83
|
+
const system = [
|
|
84
|
+
"You are configuring an autonomous coding agent's approval classifier for one user's environment.",
|
|
85
|
+
"The classifier's ruleset has an `## Environment` section of slot lines; you draft the values, plus optional extra rules.",
|
|
86
|
+
"",
|
|
87
|
+
"Return ONLY a JSON object, no prose, no code fence, with exactly these keys:",
|
|
88
|
+
' "environment": string[] — the full slot list, one entry per line, same slot names and order as the defaults given below. Keep a slot\'s default text wherever the facts show nothing better. Keep the "### Org-wide" / "### User-specific" header entries.',
|
|
89
|
+
' "allow": string[] — extra ALLOW carve-out rules, e.g. "Bash(some-cli sub:*) in <project path>". Only for routine, clearly-evidenced commands.',
|
|
90
|
+
' "soft_deny": string[] — extra SOFT BLOCK rules for hazards the facts show (things that would be costly but are clearable by explicit user intent).',
|
|
91
|
+
' "hard_deny": string[] — extra HARD BLOCK rules; almost always empty. Only for absolute boundaries the facts prove.',
|
|
92
|
+
' "notes": string[] — what you did NOT gather or could not corroborate, so the user sees the gaps.',
|
|
93
|
+
"",
|
|
94
|
+
"Evidence rules (strict):",
|
|
95
|
+
"- Assert only what the provided facts show. No fact → the slot keeps its default. Never invent organizations, domains, buckets, services, or registries.",
|
|
96
|
+
"- Facts of unverified provenance (e.g. prose in CLAUDE.md about other systems) may inform sensitivity slots and soft_deny, never trust slots or allow.",
|
|
97
|
+
"- Extra rules must make the gate tighter or carve out only narrow, routine, evidenced operations — never weaken or restate built-in rules.",
|
|
98
|
+
"- Scope carve-outs to a path (\"in <path>\") whenever the evidence is repo-specific.",
|
|
99
|
+
"- These lists are append-only additions to a fixed built-in ruleset; do not include \"$defaults\" entries, headers, or bullets — plain rule text per entry.",
|
|
100
|
+
].join("\n");
|
|
101
|
+
|
|
102
|
+
const user = [
|
|
103
|
+
"## Default Environment slots (keep any slot you cannot improve on)",
|
|
104
|
+
...defaultEnvironment.map((line) => `- ${line}`),
|
|
105
|
+
"",
|
|
106
|
+
"## Gathered facts (JSON)",
|
|
107
|
+
JSON.stringify(facts, null, 1),
|
|
108
|
+
].join("\n");
|
|
109
|
+
|
|
110
|
+
return { system, user };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** A named, actionable parse failure — the wizard shows this verbatim. */
|
|
114
|
+
export class SetupDraftError extends Error {}
|
|
115
|
+
|
|
116
|
+
const stringList = (value: unknown, key: string, required: boolean): string[] => {
|
|
117
|
+
if (value === undefined && !required) return [];
|
|
118
|
+
if (!Array.isArray(value)) throw new SetupDraftError(`draft JSON key "${key}" is not an array`);
|
|
119
|
+
const out: string[] = [];
|
|
120
|
+
for (const entry of value) {
|
|
121
|
+
if (typeof entry !== "string") throw new SetupDraftError(`draft JSON key "${key}" contains a non-string entry`);
|
|
122
|
+
const line = entry.trim();
|
|
123
|
+
if (!line) continue;
|
|
124
|
+
if (line.includes("\n")) throw new SetupDraftError(`draft JSON key "${key}" contains a multi-line entry`);
|
|
125
|
+
out.push(line);
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Parse the drafting model's reply. Strict on shape (an unusable draft must
|
|
132
|
+
* fail loud, never persist half-parsed), lenient on wrapping (a stray fence or
|
|
133
|
+
* leading prose is tolerated by extracting the outermost JSON object).
|
|
134
|
+
*
|
|
135
|
+
* When `defaultEnvironment` is given, the drafted environment must cover every
|
|
136
|
+
* built-in slot name — the wizard's environment is a full REPLACEMENT (no
|
|
137
|
+
* `$defaults` fallback exists for it), so a truncated draft that quietly drops
|
|
138
|
+
* slots the user never chose to drop is rejected, not saved.
|
|
139
|
+
*/
|
|
140
|
+
export function parseSetupDraft(text: string, defaultEnvironment: string[] = []): SetupDraft {
|
|
141
|
+
const start = text.indexOf("{");
|
|
142
|
+
const end = text.lastIndexOf("}");
|
|
143
|
+
if (start < 0 || end <= start) throw new SetupDraftError("the drafting model returned no JSON object");
|
|
144
|
+
let parsed: unknown;
|
|
145
|
+
try {
|
|
146
|
+
parsed = JSON.parse(text.slice(start, end + 1));
|
|
147
|
+
} catch (error) {
|
|
148
|
+
throw new SetupDraftError(`the drafting model returned malformed JSON (${(error as Error).message})`);
|
|
149
|
+
}
|
|
150
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
151
|
+
throw new SetupDraftError("the drafting model's JSON is not an object");
|
|
152
|
+
}
|
|
153
|
+
const record = parsed as Record<string, unknown>;
|
|
154
|
+
// "$defaults" is stripped everywhere: the prompt forbids it, and in the
|
|
155
|
+
// environment specifically a stray one would later splice the entire built-in
|
|
156
|
+
// slot block into the middle of the curated list (config.ts spliceDefaults).
|
|
157
|
+
const noDefaults = (entries: string[]) => entries.filter((entry) => entry !== "$defaults");
|
|
158
|
+
const environment = noDefaults(
|
|
159
|
+
stringList(record.environment, "environment", true).map((entry) =>
|
|
160
|
+
// Tolerate a model that bullets the lines anyway — entries are stored bare.
|
|
161
|
+
entry.startsWith("- ") ? entry.slice(2) : entry,
|
|
162
|
+
),
|
|
163
|
+
);
|
|
164
|
+
if (environment.length === 0) throw new SetupDraftError('draft JSON key "environment" is empty');
|
|
165
|
+
const drafted = new Set(environment.map(slotName).filter(Boolean));
|
|
166
|
+
const missing = defaultEnvironment.map(slotName).filter((name): name is string => !!name && !drafted.has(name));
|
|
167
|
+
if (missing.length > 0) {
|
|
168
|
+
throw new SetupDraftError(`draft environment is missing built-in slot(s): ${missing.join(", ")}`);
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
environment,
|
|
172
|
+
allow: noDefaults(stringList(record.allow, "allow", false)),
|
|
173
|
+
soft_deny: noDefaults(stringList(record.soft_deny, "soft_deny", false)),
|
|
174
|
+
hard_deny: noDefaults(stringList(record.hard_deny, "hard_deny", false)),
|
|
175
|
+
notes: stringList(record.notes, "notes", false),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The review text — CC's proposal screen, in plain notify-able form. */
|
|
180
|
+
export function renderProposal(draft: SetupDraft): string {
|
|
181
|
+
const section = (title: string, entries: string[], empty: string) =>
|
|
182
|
+
entries.length > 0 ? `${title}\n${entries.map((entry) => ` · ${entry}`).join("\n")}` : `${title}\n ${empty}`;
|
|
183
|
+
return [
|
|
184
|
+
section("Environment", draft.environment, "(none — this should not happen)"),
|
|
185
|
+
section("Allow carve-outs", draft.allow, "none suggested"),
|
|
186
|
+
section("Extra soft blocks", draft.soft_deny, "none suggested"),
|
|
187
|
+
section("Extra hard blocks", draft.hard_deny, "none suggested"),
|
|
188
|
+
...(draft.notes.length > 0 ? [section("Notes", draft.notes, "")] : []),
|
|
189
|
+
].join("\n\n");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The `autoMode` keys the wizard persists (merged over other autoMode keys by
|
|
194
|
+
* config.persistAutoModeSetup). Rule lists carry the "$defaults" sentinel the
|
|
195
|
+
* way CC's wizard writes them; lists with no extras are omitted entirely so
|
|
196
|
+
* settings stay minimal.
|
|
197
|
+
*/
|
|
198
|
+
export function settingsPatch(draft: SetupDraft): Record<string, string[] | undefined> {
|
|
199
|
+
const listOrOmit = (entries: string[]) => (entries.length > 0 ? ["$defaults", ...entries] : undefined);
|
|
200
|
+
return {
|
|
201
|
+
environment: draft.environment,
|
|
202
|
+
allow: listOrOmit(draft.allow),
|
|
203
|
+
soft_deny: listOrOmit(draft.soft_deny),
|
|
204
|
+
hard_deny: listOrOmit(draft.hard_deny),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export interface FlaggedAllowRule {
|
|
209
|
+
rule: string;
|
|
210
|
+
why: string;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* CC's "rules that skip checks" audit: permissions.allow entries broad enough
|
|
215
|
+
* to hand the model a standing way past the classifier. The judgement is the
|
|
216
|
+
* gate's own `isBroadExecutionRule` — the exact predicate auto mode already
|
|
217
|
+
* uses to suspend such rules at decision time — so the audit can never flag
|
|
218
|
+
* differently than the gate behaves. Flagging is advice; removal is the
|
|
219
|
+
* user's call.
|
|
220
|
+
*/
|
|
221
|
+
export function auditPermissionAllow(rules: string[]): FlaggedAllowRule[] {
|
|
222
|
+
const flagged: FlaggedAllowRule[] = [];
|
|
223
|
+
for (const raw of rules) {
|
|
224
|
+
const rule = parseRule(raw);
|
|
225
|
+
if (!rule || !isBroadExecutionRule(rule)) continue;
|
|
226
|
+
flagged.push({
|
|
227
|
+
rule: raw,
|
|
228
|
+
why:
|
|
229
|
+
rule.tool === "bash"
|
|
230
|
+
? "grants arbitrary code execution — matching commands never reach auto mode's checks"
|
|
231
|
+
: "pre-approves a fresh agent loop — whatever the delegated agent does is unchecked",
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return flagged;
|
|
235
|
+
}
|