@specship/specship-win32-arm64 0.22.0 → 0.23.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 (60) hide show
  1. package/lib/dist/activation/search-intercept.d.ts +59 -0
  2. package/lib/dist/activation/search-intercept.d.ts.map +1 -0
  3. package/lib/dist/activation/search-intercept.js +195 -0
  4. package/lib/dist/activation/search-intercept.js.map +1 -0
  5. package/lib/dist/bin/specship.js +70 -7
  6. package/lib/dist/bin/specship.js.map +1 -1
  7. package/lib/dist/bin/uninstall.d.ts +4 -3
  8. package/lib/dist/bin/uninstall.d.ts.map +1 -1
  9. package/lib/dist/bin/uninstall.js +4 -3
  10. package/lib/dist/bin/uninstall.js.map +1 -1
  11. package/lib/dist/installer/index.d.ts +20 -6
  12. package/lib/dist/installer/index.d.ts.map +1 -1
  13. package/lib/dist/installer/index.js +123 -63
  14. package/lib/dist/installer/index.js.map +1 -1
  15. package/lib/dist/installer/targets/claude.d.ts +9 -0
  16. package/lib/dist/installer/targets/claude.d.ts.map +1 -1
  17. package/lib/dist/installer/targets/claude.js +46 -0
  18. package/lib/dist/installer/targets/claude.js.map +1 -1
  19. package/lib/dist/installer/targets/gemini.d.ts +78 -0
  20. package/lib/dist/installer/targets/gemini.d.ts.map +1 -0
  21. package/lib/dist/installer/targets/gemini.js +200 -0
  22. package/lib/dist/installer/targets/gemini.js.map +1 -0
  23. package/lib/dist/installer/targets/registry.d.ts +5 -2
  24. package/lib/dist/installer/targets/registry.d.ts.map +1 -1
  25. package/lib/dist/installer/targets/registry.js +7 -3
  26. package/lib/dist/installer/targets/registry.js.map +1 -1
  27. package/lib/dist/installer/targets/types.d.ts +6 -2
  28. package/lib/dist/installer/targets/types.d.ts.map +1 -1
  29. package/lib/dist/jira/auto-publish.d.ts.map +1 -1
  30. package/lib/dist/jira/auto-publish.js +27 -3
  31. package/lib/dist/jira/auto-publish.js.map +1 -1
  32. package/lib/dist/jira/client.d.ts +7 -0
  33. package/lib/dist/jira/client.d.ts.map +1 -1
  34. package/lib/dist/jira/client.js +36 -1
  35. package/lib/dist/jira/client.js.map +1 -1
  36. package/lib/dist/jira/coverage.d.ts.map +1 -1
  37. package/lib/dist/jira/coverage.js +10 -0
  38. package/lib/dist/jira/coverage.js.map +1 -1
  39. package/lib/dist/jira/publish.d.ts +17 -0
  40. package/lib/dist/jira/publish.d.ts.map +1 -1
  41. package/lib/dist/jira/publish.js +0 -0
  42. package/lib/dist/jira/publish.js.map +1 -1
  43. package/lib/dist/jira/published-specs.d.ts +6 -0
  44. package/lib/dist/jira/published-specs.d.ts.map +1 -1
  45. package/lib/dist/jira/published-specs.js +50 -9
  46. package/lib/dist/jira/published-specs.js.map +1 -1
  47. package/lib/dist/jira/repo-config.d.ts +14 -0
  48. package/lib/dist/jira/repo-config.d.ts.map +1 -1
  49. package/lib/dist/jira/repo-config.js +27 -0
  50. package/lib/dist/jira/repo-config.js.map +1 -1
  51. package/lib/dist/mcp/jira-tools.d.ts.map +1 -1
  52. package/lib/dist/mcp/jira-tools.js +20 -4
  53. package/lib/dist/mcp/jira-tools.js.map +1 -1
  54. package/lib/dist/mcp/tools.d.ts +50 -0
  55. package/lib/dist/mcp/tools.d.ts.map +1 -1
  56. package/lib/dist/mcp/tools.js +205 -8
  57. package/lib/dist/mcp/tools.js.map +1 -1
  58. package/lib/node_modules/.package-lock.json +1 -1
  59. package/lib/package.json +1 -1
  60. package/package.json +1 -1
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Point-of-use search interceptor (STEER-HOOK-DOC, REQ-STEER-004/005).
3
+ *
4
+ * The prompt-level nudge (steering.ts) fires once, at prompt submit, and its
5
+ * pull decays across a long turn — the observed failure is a search-shaped
6
+ * tool call made many tool-calls after the nudge. This module is the decision
7
+ * logic for the `PreToolUse` hook the installer writes on search tools
8
+ * (`specship search-intercept`): deliver the same guidance at the moment of
9
+ * the wrong choice.
10
+ *
11
+ * Two properties are non-negotiable:
12
+ *
13
+ * - ADVISORY (REQ-STEER-004): the emitted text is `additionalContext`,
14
+ * never a permission decision. A search the index cannot answer is a
15
+ * legitimate search; a wrong denial costs the agent a turn — the very
16
+ * failure this feature exists to reduce.
17
+ * - SELF-SILENCING (REQ-STEER-005): at most one line per session, and only
18
+ * when the session has made zero specship tool calls. Firing on every
19
+ * search would make the hook noise, and noise is what taught the agent to
20
+ * ignore the prompt-level nudge.
21
+ */
22
+ /**
23
+ * The one redirect line. Explicitly advisory ("your search still runs") so
24
+ * the model doesn't treat it as a rejection of the call it just made.
25
+ */
26
+ export declare const SEARCH_INTERCEPT_TEXT: string;
27
+ /** Per-session tracking file, kept inside the project's own `.specship/`. */
28
+ export declare function interceptStatePath(projectRoot: string): string;
29
+ export interface SearchInterceptInput {
30
+ /** Project the intercepted call belongs to (the hook payload's `cwd`). */
31
+ cwd: string;
32
+ /** Claude Code session id from the hook payload; null → untrackable, stay silent. */
33
+ sessionId: string | null;
34
+ /** Session transcript path from the hook payload, when present. */
35
+ transcriptPath: string | null;
36
+ env?: NodeJS.ProcessEnv;
37
+ homedir?: string;
38
+ /** Injectable clock for tests. */
39
+ now?: Date;
40
+ }
41
+ /**
42
+ * Decide whether to emit the redirect line for one intercepted search call.
43
+ *
44
+ * Silence gates, in order (all REQ-STEER-004.A3 / REQ-STEER-005):
45
+ * 1. `SPECSHIP_NO_STEERING=1` (env > project > install settings) — same
46
+ * opt-out as the prompt nudge.
47
+ * 2. No `.specship/` directory at cwd — uninitialized projects get zero
48
+ * noise.
49
+ * 3. No session id — nothing to key the once-per-session guarantee on, so
50
+ * silence beats repeat-firing.
51
+ * 4. The session is already recorded — it fired before, or was seen using
52
+ * specship.
53
+ * 5. The transcript shows specship use — the agent doesn't need redirecting;
54
+ * record the session so later calls skip the scan.
55
+ *
56
+ * Otherwise: record the session and return the line (fires exactly once).
57
+ */
58
+ export declare function buildSearchIntercept(input: SearchInterceptInput): string | null;
59
+ //# sourceMappingURL=search-intercept.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-intercept.d.ts","sourceRoot":"","sources":["../../src/activation/search-intercept.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAIX,CAAC;AAExB,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE9D;AA8ED,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAC;IACZ,qFAAqF;IACrF,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mEAAmE;IACnE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,GAAG,IAAI,CAwB/E"}
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ /**
3
+ * Point-of-use search interceptor (STEER-HOOK-DOC, REQ-STEER-004/005).
4
+ *
5
+ * The prompt-level nudge (steering.ts) fires once, at prompt submit, and its
6
+ * pull decays across a long turn — the observed failure is a search-shaped
7
+ * tool call made many tool-calls after the nudge. This module is the decision
8
+ * logic for the `PreToolUse` hook the installer writes on search tools
9
+ * (`specship search-intercept`): deliver the same guidance at the moment of
10
+ * the wrong choice.
11
+ *
12
+ * Two properties are non-negotiable:
13
+ *
14
+ * - ADVISORY (REQ-STEER-004): the emitted text is `additionalContext`,
15
+ * never a permission decision. A search the index cannot answer is a
16
+ * legitimate search; a wrong denial costs the agent a turn — the very
17
+ * failure this feature exists to reduce.
18
+ * - SELF-SILENCING (REQ-STEER-005): at most one line per session, and only
19
+ * when the session has made zero specship tool calls. Firing on every
20
+ * search would make the hook noise, and noise is what taught the agent to
21
+ * ignore the prompt-level nudge.
22
+ */
23
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ var desc = Object.getOwnPropertyDescriptor(m, k);
26
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
27
+ desc = { enumerable: true, get: function() { return m[k]; } };
28
+ }
29
+ Object.defineProperty(o, k2, desc);
30
+ }) : (function(o, m, k, k2) {
31
+ if (k2 === undefined) k2 = k;
32
+ o[k2] = m[k];
33
+ }));
34
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
35
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
36
+ }) : function(o, v) {
37
+ o["default"] = v;
38
+ });
39
+ var __importStar = (this && this.__importStar) || (function () {
40
+ var ownKeys = function(o) {
41
+ ownKeys = Object.getOwnPropertyNames || function (o) {
42
+ var ar = [];
43
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
44
+ return ar;
45
+ };
46
+ return ownKeys(o);
47
+ };
48
+ return function (mod) {
49
+ if (mod && mod.__esModule) return mod;
50
+ var result = {};
51
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
52
+ __setModuleDefault(result, mod);
53
+ return result;
54
+ };
55
+ })();
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.SEARCH_INTERCEPT_TEXT = void 0;
58
+ exports.interceptStatePath = interceptStatePath;
59
+ exports.buildSearchIntercept = buildSearchIntercept;
60
+ const fs = __importStar(require("fs"));
61
+ const path = __importStar(require("path"));
62
+ const runtime_settings_1 = require("../config/runtime-settings");
63
+ /**
64
+ * The one redirect line. Explicitly advisory ("your search still runs") so
65
+ * the model doesn't treat it as a rejection of the call it just made.
66
+ */
67
+ exports.SEARCH_INTERCEPT_TEXT = 'SpecShip: this project has an indexed code graph. For finding or understanding code, ' +
68
+ 'mcp__specship__specship_explore with the relevant symbol/file names usually answers in ' +
69
+ 'ONE call and returns the verbatim source. Your search still runs — reach for explore ' +
70
+ 'on the next lookup.';
71
+ /** Per-session tracking file, kept inside the project's own `.specship/`. */
72
+ function interceptStatePath(projectRoot) {
73
+ return path.join(projectRoot, '.specship', 'steer-intercept-state.json');
74
+ }
75
+ /** Records older than this are pruned on write (REQ-STEER-005.A4). */
76
+ const STATE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
77
+ function readState(file) {
78
+ try {
79
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
80
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
81
+ return {};
82
+ const out = {};
83
+ for (const [k, v] of Object.entries(parsed)) {
84
+ if (v && typeof v === 'object' && typeof v.at === 'string') {
85
+ out[k] = { at: v.at };
86
+ }
87
+ }
88
+ return out;
89
+ }
90
+ catch {
91
+ return {};
92
+ }
93
+ }
94
+ /** Best-effort write with TTL pruning; a failed write must never fail the hook. */
95
+ function writeState(file, state, now) {
96
+ try {
97
+ const pruned = {};
98
+ for (const [k, v] of Object.entries(state)) {
99
+ const at = Date.parse(v.at);
100
+ if (Number.isFinite(at) && now.getTime() - at < STATE_TTL_MS)
101
+ pruned[k] = v;
102
+ }
103
+ fs.writeFileSync(file, JSON.stringify(pruned));
104
+ }
105
+ catch {
106
+ /* state is an optimization; losing it only risks one extra advisory line */
107
+ }
108
+ }
109
+ /**
110
+ * Whether the session's transcript shows any specship tool USE. A transcript
111
+ * record must be an actual `tool_use` naming a specship tool — a bare
112
+ * mention of a tool name is NOT use: transcripts routinely carry the full
113
+ * tool listing (`deferred_tools_delta` attachments, skill listings), so a
114
+ * substring test alone silences the hook in every session where specship is
115
+ * merely INSTALLED (found live in the REQ-STEER-006 A/B — the interceptor
116
+ * never fired). Reads at most the trailing 4 MB — usage anywhere counts, but
117
+ * a huge transcript must not blow the hook's latency budget, and a false
118
+ * negative costs one harmless advisory line.
119
+ */
120
+ function transcriptUsedSpecship(transcriptPath) {
121
+ const MAX_SCAN_BYTES = 4 * 1024 * 1024;
122
+ let text;
123
+ try {
124
+ const stat = fs.statSync(transcriptPath);
125
+ if (stat.size <= MAX_SCAN_BYTES) {
126
+ text = fs.readFileSync(transcriptPath, 'utf-8');
127
+ }
128
+ else {
129
+ const fd = fs.openSync(transcriptPath, 'r');
130
+ try {
131
+ const buf = Buffer.alloc(MAX_SCAN_BYTES);
132
+ const read = fs.readSync(fd, buf, 0, MAX_SCAN_BYTES, stat.size - MAX_SCAN_BYTES);
133
+ text = buf.toString('utf-8', 0, read);
134
+ }
135
+ finally {
136
+ fs.closeSync(fd);
137
+ }
138
+ }
139
+ }
140
+ catch {
141
+ return false;
142
+ }
143
+ // One JSONL record per line; a specship tool_use block puts both markers on
144
+ // the same record. Cheap two-substring test — no JSON.parse per line.
145
+ for (const line of text.split('\n')) {
146
+ if (line.includes('"type":"tool_use"') && line.includes('"name":"mcp__specship__')) {
147
+ return true;
148
+ }
149
+ }
150
+ return false;
151
+ }
152
+ /**
153
+ * Decide whether to emit the redirect line for one intercepted search call.
154
+ *
155
+ * Silence gates, in order (all REQ-STEER-004.A3 / REQ-STEER-005):
156
+ * 1. `SPECSHIP_NO_STEERING=1` (env > project > install settings) — same
157
+ * opt-out as the prompt nudge.
158
+ * 2. No `.specship/` directory at cwd — uninitialized projects get zero
159
+ * noise.
160
+ * 3. No session id — nothing to key the once-per-session guarantee on, so
161
+ * silence beats repeat-firing.
162
+ * 4. The session is already recorded — it fired before, or was seen using
163
+ * specship.
164
+ * 5. The transcript shows specship use — the agent doesn't need redirecting;
165
+ * record the session so later calls skip the scan.
166
+ *
167
+ * Otherwise: record the session and return the line (fires exactly once).
168
+ */
169
+ function buildSearchIntercept(input) {
170
+ const { cwd, sessionId, transcriptPath, env = process.env, homedir, now = new Date() } = input;
171
+ if ((0, runtime_settings_1.resolveSetting)('SPECSHIP_NO_STEERING', cwd, env, homedir) === '1')
172
+ return null;
173
+ try {
174
+ if (!fs.statSync(path.join(cwd, '.specship')).isDirectory())
175
+ return null;
176
+ }
177
+ catch {
178
+ return null;
179
+ }
180
+ if (!sessionId)
181
+ return null;
182
+ const stateFile = interceptStatePath(cwd);
183
+ const state = readState(stateFile);
184
+ if (state[sessionId])
185
+ return null;
186
+ state[sessionId] = { at: now.toISOString() };
187
+ if (transcriptPath && transcriptUsedSpecship(transcriptPath)) {
188
+ // Already an index user — silence, but remember so we don't rescan.
189
+ writeState(stateFile, state, now);
190
+ return null;
191
+ }
192
+ writeState(stateFile, state, now);
193
+ return exports.SEARCH_INTERCEPT_TEXT;
194
+ }
195
+ //# sourceMappingURL=search-intercept.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-intercept.js","sourceRoot":"","sources":["../../src/activation/search-intercept.ts"],"names":[],"mappings":"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"}
@@ -2468,14 +2468,25 @@ function main() {
2468
2468
  .option('--statusline', 'Wire the SpecShip status-line segment into Claude (skips the prompt; never overwrites an existing status line)')
2469
2469
  .option('--skip-statusline', 'Do not add the status-line segment (skips the prompt)')
2470
2470
  .option('--skip-index', 'Do not offer to index the current project (an explicit opt-out for automation)')
2471
- .option('--print-config', 'Print MCP config snippet for Claude Code and exit (no file writes)')
2472
- // -t/--target is vestigial — kept so existing `--target claude` / `--target auto`
2473
- // invocations (including our own offline-install scripts) keep working.
2474
- .option('-t, --target <ids>', '(vestigial) accepted: "claude" | "auto" | "all" | "none"')
2471
+ .option('--print-config', 'Print the MCP config snippet and exit (no file writes). Claude Code by default; `--target gemini` prints the Gemini CLI settings snippet')
2472
+ // -t/--target SELECTS the agents to wire (GEMINI-TARGET-DOC, REQ-GEMINI-002)
2473
+ // and also picks the snippet --print-config prints. The legacy spellings
2474
+ // ("claude" / "auto" / "all" / "none") keep their Claude-only meanings, so
2475
+ // existing invocations — including our own offline-install scripts — behave
2476
+ // exactly as before.
2477
+ .option('-t, --target <ids>', 'Agents to wire, comma-separated: "claude" (default) | "gemini" | "claude,gemini". Also selects the snippet `--print-config` prints. Legacy: "auto"/"all" = claude, "none" = skip')
2475
2478
  .action(async (opts) => {
2476
2479
  if (opts.printConfig) {
2477
- const { claudeTarget } = await Promise.resolve().then(() => __importStar(require('../installer/targets/claude')));
2478
2480
  const loc = (opts.location === 'local' ? 'local' : 'global');
2481
+ // Only `gemini` selects the non-default snippet; every other value
2482
+ // (including the vestigial "auto" / "all" / "none") stays Claude, so
2483
+ // existing invocations print exactly what they printed before.
2484
+ if (opts.target === 'gemini') {
2485
+ const { geminiTarget } = await Promise.resolve().then(() => __importStar(require('../installer/targets/gemini')));
2486
+ process.stdout.write(geminiTarget.printConfig(loc));
2487
+ return;
2488
+ }
2489
+ const { claudeTarget } = await Promise.resolve().then(() => __importStar(require('../installer/targets/claude')));
2479
2490
  process.stdout.write(claudeTarget.printConfig(loc));
2480
2491
  return;
2481
2492
  }
@@ -2574,9 +2585,13 @@ function main() {
2574
2585
  console.log(chalk.dim(' (advisory — diagnose anytime with `specship doctor`)'));
2575
2586
  }
2576
2587
  // Restart reminder (REQ-HANDSHAKE-001): an MCP server added to the config
2577
- // is NOT visible to a Claude Code session that was already open.
2588
+ // is NOT visible to an agent session that was already open. Names the
2589
+ // agents actually wired, so a `--target gemini` install doesn't tell the
2590
+ // user to restart Claude Code.
2591
+ const { resolveInstallTargets } = await Promise.resolve().then(() => __importStar(require('../installer')));
2592
+ const agents = resolveInstallTargets(opts.target).map((t) => t.displayName).join(' / ');
2578
2593
  console.log();
2579
- info('Restart Claude Code (or run `/mcp`) to load the SpecShip server — it is not visible in a session that is already open.');
2594
+ info(`Restart ${agents} (or run \`/mcp\`) to load the SpecShip server — it is not visible in a session that is already open.`);
2580
2595
  }
2581
2596
  catch (err) {
2582
2597
  error(err instanceof Error ? err.message : String(err));
@@ -2804,6 +2819,54 @@ function main() {
2804
2819
  }
2805
2820
  process.exit(0);
2806
2821
  });
2822
+ /**
2823
+ * specship search-intercept (STEER-HOOK-DOC, REQ-STEER-004/005)
2824
+ *
2825
+ * Internal PreToolUse hook command on search-shaped tools (Grep/Glob): emit
2826
+ * the point-of-use redirect to specship_explore, at most once per session and
2827
+ * only when the session hasn't used specship yet. STRICTLY ADVISORY — the
2828
+ * output is additionalContext, never a permission decision, and the command
2829
+ * exits 0 on every path (including internal errors) so the matched call
2830
+ * always proceeds (REQ-STEER-004.A2).
2831
+ */
2832
+ program
2833
+ .command('search-intercept')
2834
+ .description('Internal hook: point-of-use redirect from search tools to specship_explore (PreToolUse)')
2835
+ .action(async () => {
2836
+ try {
2837
+ let cwd = process.cwd();
2838
+ let sessionId = null;
2839
+ let transcriptPath = null;
2840
+ const chunks = [];
2841
+ try {
2842
+ for await (const c of process.stdin)
2843
+ chunks.push(c);
2844
+ }
2845
+ catch { /* no stdin — fall back to process.cwd(), untracked session */ }
2846
+ const raw = Buffer.concat(chunks).toString('utf-8').trim();
2847
+ if (raw) {
2848
+ try {
2849
+ const parsed = JSON.parse(raw);
2850
+ if (typeof parsed?.cwd === 'string' && parsed.cwd.length > 0)
2851
+ cwd = parsed.cwd;
2852
+ if (typeof parsed?.session_id === 'string' && parsed.session_id.length > 0) {
2853
+ sessionId = parsed.session_id;
2854
+ }
2855
+ if (typeof parsed?.transcript_path === 'string' && parsed.transcript_path.length > 0) {
2856
+ transcriptPath = parsed.transcript_path;
2857
+ }
2858
+ }
2859
+ catch { /* not JSON — keep defaults */ }
2860
+ }
2861
+ const { buildSearchIntercept } = await Promise.resolve().then(() => __importStar(require('../activation/search-intercept')));
2862
+ const additionalContext = buildSearchIntercept({ cwd, sessionId, transcriptPath });
2863
+ if (additionalContext) {
2864
+ process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext } }) + '\n');
2865
+ }
2866
+ }
2867
+ catch { /* advisory only — never block the matched call (REQ-STEER-004.A2) */ }
2868
+ process.exit(0);
2869
+ });
2807
2870
  /**
2808
2871
  * specship uninstall
2809
2872
  *