linksee-memory 0.16.0 → 0.17.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/dist/bin/setup.js CHANGED
@@ -49,20 +49,20 @@ const noGuard = args.includes('--no-guard');
49
49
  const projectGuard = args.includes('--project-guard');
50
50
  const showHelp = args.includes('--help') || args.includes('-h');
51
51
  if (showHelp) {
52
- console.log(`linksee-memory-setup — One-command setup for Linksee Memory
53
-
54
- Usage:
55
- npx linksee-memory setup Interactive setup
56
- npx linksee-memory setup --yes Accept all defaults, no prompts
57
- npx linksee-memory setup --dry-run Show what would happen
58
-
59
- What it does:
60
- 1. Registers linksee-memory MCP server with Claude Code
61
- 2. Installs SKILL.md (teaches the agent when to recall/remember)
62
- 3. Configures Stop hook (auto-captures every session)
63
- 4. Offers to wire the re-injection guard into THIS project's .claude/settings.json
64
-
65
- After setup, just chat with Claude Code normally.
52
+ console.log(`linksee-memory-setup — One-command setup for Linksee Memory
53
+
54
+ Usage:
55
+ npx linksee-memory setup Interactive setup
56
+ npx linksee-memory setup --yes Accept all defaults, no prompts
57
+ npx linksee-memory setup --dry-run Show what would happen
58
+
59
+ What it does:
60
+ 1. Registers linksee-memory MCP server with Claude Code
61
+ 2. Installs SKILL.md (teaches the agent when to recall/remember)
62
+ 3. Configures Stop hook (auto-captures every session)
63
+ 4. Offers to wire the re-injection guard into THIS project's .claude/settings.json
64
+
65
+ After setup, just chat with Claude Code normally.
66
66
  Add "Use Linksee" to any prompt to trigger memory recall.`);
67
67
  process.exit(0);
68
68
  }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ // linksee-memory share-draft — print a link to KanseiLINK's reviewed draft page for a field note.
3
+ // Same checks and the same builder as the MCP tool `share_draft` (src/lib/share-draft.ts).
4
+ // Makes no network call and writes nothing: it prints a URL, and you open it yourself.
5
+ //
6
+ // npx -y linksee-memory share-draft --help
7
+ // npx -y linksee-memory share-draft --service freee --task "..." --symptom "..." \
8
+ // --tried "..." --result "..." --kind execution [--conditions "..."] [--runtime claude] [--json]
9
+ import { buildShareDraft, ALLOWED_KEYS, KINDS, RUNTIMES, NOTICE, NOTICE_EN, DRAFT_PAGE, URL_MAX } from '../lib/share-draft.js';
10
+ const HELP = `linksee-memory share-draft — make a link to a reviewed draft of a fix, for other agents
11
+
12
+ Linksee sends nothing. This prints a URL to ${DRAFT_PAGE}
13
+ The page opens with the fields filled; you read them there, and only your click sends.
14
+ If you never run this (or the MCP tool share_draft), nothing happens.
15
+
16
+ Usage:
17
+ npx -y linksee-memory share-draft --service <id> --task <text> --symptom <text> \\
18
+ --tried <text> --result <text> --kind <${KINDS.join('|')}> \\
19
+ [--conditions <text>] [--runtime <${RUNTIMES.join('|')}>] [--json]
20
+
21
+ Fields (nothing else is accepted):
22
+ --service KanseiLINK service_id, lowercase: ^[a-z0-9_.-]{1,160}$ (required)
23
+ --task what you were trying to do, ≤300 chars (required)
24
+ --symptom the KIND of error, no values, ≤600 chars (required)
25
+ --tried steps you tried, ≤2,000 chars (required)
26
+ --result how it was solved / that it was not, ≤1,000 chars (required)
27
+ --conditions tool + version, kind of environment, ≤600 chars
28
+ --kind execution | documentation | hypothesis (required)
29
+ --runtime claude | codex | gpt | gemini | other
30
+
31
+ Stops (no link) when a field looks like a token / API key / JWT / Bearer value, an e-mail,
32
+ a tenant URL (acme.slack.com …), an IP address, a local path (C:\\, /Users/, /home/), or a
33
+ long hex / base64 run (32+), and says which field. The whole URL must be ≤${URL_MAX} chars
34
+ (one Japanese character takes 9) — it asks you to shorten; it never cuts.
35
+
36
+ ${NOTICE[0]}
37
+ ${NOTICE[1]}
38
+ `;
39
+ function parse(argv) {
40
+ const fields = {};
41
+ const bad = [];
42
+ let json = false;
43
+ let help = false;
44
+ for (let i = 0; i < argv.length; i++) {
45
+ const a = argv[i];
46
+ if (a === '-h' || a === '--help') {
47
+ help = true;
48
+ continue;
49
+ }
50
+ if (a === '--json') {
51
+ json = true;
52
+ continue;
53
+ }
54
+ const m = /^--([a-z_]+)(?:=(.*))?$/s.exec(a);
55
+ if (!m) {
56
+ bad.push(a);
57
+ continue;
58
+ }
59
+ const key = m[1];
60
+ const value = m[2] ?? argv[++i];
61
+ if (value === undefined) {
62
+ bad.push(a);
63
+ continue;
64
+ }
65
+ fields[key] = value; // unknown keys are reported by the builder
66
+ }
67
+ return { fields, json, help, bad };
68
+ }
69
+ const { fields, json, help, bad } = parse(process.argv.slice(2));
70
+ if (help || (Object.keys(fields).length === 0 && bad.length === 0)) {
71
+ process.stdout.write(HELP);
72
+ process.exit(0);
73
+ }
74
+ if (bad.length) {
75
+ process.stderr.write(`Could not read: ${bad.join(' ')}\nFields: ${ALLOWED_KEYS.map((k) => '--' + k).join(' ')}\n`);
76
+ process.exit(2);
77
+ }
78
+ const r = buildShareDraft(fields);
79
+ if (json) {
80
+ process.stdout.write(JSON.stringify(r, null, 2) + '\n');
81
+ process.exit(r.ok ? 0 : 1);
82
+ }
83
+ if (!r.ok) {
84
+ process.stderr.write('No link made.\n');
85
+ for (const p of r.problems)
86
+ process.stderr.write(` ${p.field}: ${p.reason}\n`);
87
+ process.exit(1);
88
+ }
89
+ process.stdout.write(`${r.url}\n\n${NOTICE[0]}\n${NOTICE[1]}\n${NOTICE_EN[0]}\n${NOTICE_EN[1]}\n`);
90
+ //# sourceMappingURL=share-draft.js.map
@@ -70,11 +70,11 @@ export function runMigrations(db) {
70
70
  // v3 → v4: rebuild memories_fts with trigram tokenizer for JP/CJK support.
71
71
  // Only runs when upgrading an existing DB from schema v1-3.
72
72
  if (currentVersion > 0 && currentVersion < 4) {
73
- db.exec(`
74
- DROP TRIGGER IF EXISTS trg_memories_fts_ai;
75
- DROP TRIGGER IF EXISTS trg_memories_fts_ad;
76
- DROP TRIGGER IF EXISTS trg_memories_fts_au;
77
- DROP TABLE IF EXISTS memories_fts;
73
+ db.exec(`
74
+ DROP TRIGGER IF EXISTS trg_memories_fts_ai;
75
+ DROP TRIGGER IF EXISTS trg_memories_fts_ad;
76
+ DROP TRIGGER IF EXISTS trg_memories_fts_au;
77
+ DROP TABLE IF EXISTS memories_fts;
78
78
  `);
79
79
  }
80
80
  // v4 → v5: add normalized_name column BEFORE schema.sql runs,
@@ -118,9 +118,9 @@ export function runMigrations(db) {
118
118
  db.exec('ALTER TABLE memories ADD COLUMN thread_id TEXT');
119
119
  }
120
120
  // Backfill thread_id from content JSON session_id for existing memories
121
- db.exec(`
122
- UPDATE memories SET thread_id = json_extract(content, '$.session_id')
123
- WHERE thread_id IS NULL AND json_valid(content) AND json_extract(content, '$.session_id') IS NOT NULL
121
+ db.exec(`
122
+ UPDATE memories SET thread_id = json_extract(content, '$.session_id')
123
+ WHERE thread_id IS NULL AND json_valid(content) AND json_extract(content, '$.session_id') IS NOT NULL
124
124
  `);
125
125
  }
126
126
  // v8 → v9: ProjectCoreNode — extend drift_anchors into the Current Truth Map node.
@@ -221,12 +221,12 @@ function migrateV5EntityNormalization(db) {
221
221
  }
222
222
  })();
223
223
  // 4. Auto-merge duplicate entities (same kind + normalized_name)
224
- const dupes = db.prepare(`
225
- SELECT kind, normalized_name, GROUP_CONCAT(id) as ids
226
- FROM entities
227
- WHERE normalized_name IS NOT NULL
228
- GROUP BY kind, normalized_name
229
- HAVING COUNT(*) > 1
224
+ const dupes = db.prepare(`
225
+ SELECT kind, normalized_name, GROUP_CONCAT(id) as ids
226
+ FROM entities
227
+ WHERE normalized_name IS NOT NULL
228
+ GROUP BY kind, normalized_name
229
+ HAVING COUNT(*) > 1
230
230
  `).all();
231
231
  if (dupes.length > 0) {
232
232
  console.log(`[linksee-memory] v5 migration: merging ${dupes.length} duplicate entity clusters`);
@@ -247,12 +247,12 @@ function mergeEntityCluster(db, ids) {
247
247
  if (ids.length < 2)
248
248
  return;
249
249
  // Score each entity: prefer most memories, then has canonical_key, then lowest id
250
- const rows = db.prepare(`
251
- SELECT e.id, e.name, e.canonical_key, COUNT(m.id) as mem_count
252
- FROM entities e LEFT JOIN memories m ON m.entity_id = e.id
253
- WHERE e.id IN (${ids.map(() => '?').join(',')})
254
- GROUP BY e.id
255
- ORDER BY mem_count DESC, (e.canonical_key IS NOT NULL) DESC, e.id ASC
250
+ const rows = db.prepare(`
251
+ SELECT e.id, e.name, e.canonical_key, COUNT(m.id) as mem_count
252
+ FROM entities e LEFT JOIN memories m ON m.entity_id = e.id
253
+ WHERE e.id IN (${ids.map(() => '?').join(',')})
254
+ GROUP BY e.id
255
+ ORDER BY mem_count DESC, (e.canonical_key IS NOT NULL) DESC, e.id ASC
256
256
  `).all(...ids);
257
257
  const keep = rows[0];
258
258
  const mergeIds = rows.slice(1).map(r => r.id);
@@ -273,16 +273,16 @@ function mergeEntityCluster(db, ids) {
273
273
  db.prepare('UPDATE events SET entity_id = ? WHERE entity_id = ?').run(keep.id, mid);
274
274
  // Reassign edges (both directions)
275
275
  // Handle UNIQUE constraint: delete duplicates first
276
- db.prepare(`
277
- DELETE FROM edges WHERE from_id = ? AND EXISTS (
278
- SELECT 1 FROM edges e2 WHERE e2.from_id = ? AND e2.to_id = edges.to_id AND e2.relation = edges.relation
279
- )
276
+ db.prepare(`
277
+ DELETE FROM edges WHERE from_id = ? AND EXISTS (
278
+ SELECT 1 FROM edges e2 WHERE e2.from_id = ? AND e2.to_id = edges.to_id AND e2.relation = edges.relation
279
+ )
280
280
  `).run(mid, keep.id);
281
281
  db.prepare('UPDATE edges SET from_id = ? WHERE from_id = ?').run(keep.id, mid);
282
- db.prepare(`
283
- DELETE FROM edges WHERE to_id = ? AND EXISTS (
284
- SELECT 1 FROM edges e2 WHERE e2.to_id = ? AND e2.from_id = edges.from_id AND e2.relation = edges.relation
285
- )
282
+ db.prepare(`
283
+ DELETE FROM edges WHERE to_id = ? AND EXISTS (
284
+ SELECT 1 FROM edges e2 WHERE e2.to_id = ? AND e2.from_id = edges.from_id AND e2.relation = edges.relation
285
+ )
286
286
  `).run(mid, keep.id);
287
287
  db.prepare('UPDATE edges SET to_id = ? WHERE to_id = ?').run(keep.id, mid);
288
288
  // Reassign consolidations
@@ -74,9 +74,9 @@ export function declareAnchor(db, input) {
74
74
  throw new Error(`${input.kind} anchors need at least one violation_signal term (the forbidden act / rejected alternative) — otherwise the detector can never deduce a contradiction`);
75
75
  }
76
76
  const info = db
77
- .prepare(`INSERT INTO drift_anchors
78
- (kind, statement, rationale, affects, detect_terms, violation_signal, tier, source, source_memory_id)
79
- VALUES
77
+ .prepare(`INSERT INTO drift_anchors
78
+ (kind, statement, rationale, affects, detect_terms, violation_signal, tier, source, source_memory_id)
79
+ VALUES
80
80
  (@kind, @statement, @rationale, @affects, @detect_terms, @violation_signal, @tier, @source, @source_memory_id)`)
81
81
  .run({
82
82
  kind: input.kind,
@@ -110,7 +110,7 @@ export function detectDrift(db, opts = {}) {
110
110
  samples: [],
111
111
  };
112
112
  const anchors = db
113
- .prepare(`SELECT id, kind, statement, affects, detect_terms, violation_signal, tier, created_at
113
+ .prepare(`SELECT id, kind, statement, affects, detect_terms, violation_signal, tier, created_at
114
114
  FROM drift_anchors WHERE status = 'active'`)
115
115
  .all();
116
116
  res.anchorsScanned = anchors.length;
@@ -120,8 +120,8 @@ export function detectDrift(db, opts = {}) {
120
120
  // the "why this edit" text, not just path + snippet. (session_file_edits.memory_id →
121
121
  // memories.id; memories_fts.rowid = memories.id.)
122
122
  const edits = db
123
- .prepare(`SELECT e.id, e.file_path, e.context_snippet, e.memory_id, m.content AS memory_content, e.occurred_at
124
- FROM session_file_edits e
123
+ .prepare(`SELECT e.id, e.file_path, e.context_snippet, e.memory_id, m.content AS memory_content, e.occurred_at
124
+ FROM session_file_edits e
125
125
  LEFT JOIN memories m ON m.id = e.memory_id`)
126
126
  .all();
127
127
  res.editsScanned = edits.length;
@@ -137,19 +137,19 @@ export function detectDrift(db, opts = {}) {
137
137
  // Write paths prepared only when persisting — keeps dryRun safe on a readonly connection.
138
138
  const upsertContradicts = opts.dryRun
139
139
  ? null
140
- : db.prepare(`
141
- INSERT INTO drift_edges (anchor_id, edit_id, verdict, confidence, evidence, status)
142
- VALUES (@anchor_id, @edit_id, 'contradicts', @confidence, @evidence, 'open')
143
- ON CONFLICT(anchor_id, edit_id, verdict) DO UPDATE SET
144
- confidence = excluded.confidence,
145
- evidence = excluded.evidence
146
- WHERE drift_edges.status = 'open'
140
+ : db.prepare(`
141
+ INSERT INTO drift_edges (anchor_id, edit_id, verdict, confidence, evidence, status)
142
+ VALUES (@anchor_id, @edit_id, 'contradicts', @confidence, @evidence, 'open')
143
+ ON CONFLICT(anchor_id, edit_id, verdict) DO UPDATE SET
144
+ confidence = excluded.confidence,
145
+ evidence = excluded.evidence
146
+ WHERE drift_edges.status = 'open'
147
147
  `);
148
148
  const insertAbsent = opts.dryRun
149
149
  ? null
150
- : db.prepare(`
151
- INSERT OR IGNORE INTO drift_edges (anchor_id, edit_id, verdict, confidence, evidence, status)
152
- VALUES (@anchor_id, NULL, 'absent', @confidence, @evidence, 'open')
150
+ : db.prepare(`
151
+ INSERT OR IGNORE INTO drift_edges (anchor_id, edit_id, verdict, confidence, evidence, status)
152
+ VALUES (@anchor_id, NULL, 'absent', @confidence, @evidence, 'open')
153
153
  `);
154
154
  const apply = () => {
155
155
  for (const a of anchors) {
@@ -351,13 +351,13 @@ export function detectFileViolations(db, opts = {}) {
351
351
  .all();
352
352
  const upsert = opts.dryRun
353
353
  ? null
354
- : db.prepare(`
355
- INSERT INTO drift_edges (anchor_id, edit_id, verdict, confidence, evidence, status)
356
- VALUES (@anchor_id, @edit_id, 'contradicts', @confidence, @evidence, 'open')
357
- ON CONFLICT(anchor_id, edit_id, verdict) DO UPDATE SET
358
- confidence = excluded.confidence,
359
- evidence = excluded.evidence
360
- WHERE drift_edges.status = 'open'
354
+ : db.prepare(`
355
+ INSERT INTO drift_edges (anchor_id, edit_id, verdict, confidence, evidence, status)
356
+ VALUES (@anchor_id, @edit_id, 'contradicts', @confidence, @evidence, 'open')
357
+ ON CONFLICT(anchor_id, edit_id, verdict) DO UPDATE SET
358
+ confidence = excluded.confidence,
359
+ evidence = excluded.evidence
360
+ WHERE drift_edges.status = 'open'
361
361
  `);
362
362
  const apply = () => {
363
363
  for (const a of anchors) {
@@ -27,14 +27,14 @@ function parseEvidence(s) {
27
27
  export function getDriftHeadline(db, opts = {}) {
28
28
  const limit = opts.limit ?? 3;
29
29
  const rows = db
30
- .prepare(`SELECT d.id AS edge_id, d.anchor_id, a.kind, a.statement, a.rationale,
31
- e.file_path, e.context_snippet, e.occurred_at,
32
- d.confidence, d.evidence
33
- FROM drift_edges d
34
- JOIN drift_anchors a ON a.id = d.anchor_id
35
- JOIN session_file_edits e ON e.id = d.edit_id
36
- WHERE d.verdict = 'contradicts' AND d.status = 'open' AND a.status = 'active'
37
- ORDER BY d.confidence DESC, (a.tier = 'human') DESC, e.occurred_at DESC
30
+ .prepare(`SELECT d.id AS edge_id, d.anchor_id, a.kind, a.statement, a.rationale,
31
+ e.file_path, e.context_snippet, e.occurred_at,
32
+ d.confidence, d.evidence
33
+ FROM drift_edges d
34
+ JOIN drift_anchors a ON a.id = d.anchor_id
35
+ JOIN session_file_edits e ON e.id = d.edit_id
36
+ WHERE d.verdict = 'contradicts' AND d.status = 'open' AND a.status = 'active'
37
+ ORDER BY d.confidence DESC, (a.tier = 'human') DESC, e.occurred_at DESC
38
38
  LIMIT ?`)
39
39
  .all(limit);
40
40
  return rows.map((r) => {
@@ -61,12 +61,12 @@ export function getDriftHeadline(db, opts = {}) {
61
61
  export function getDriftAbsences(db, opts = {}) {
62
62
  const limit = opts.limit ?? 10;
63
63
  const rows = db
64
- .prepare(`SELECT d.id AS edge_id, d.anchor_id, a.kind, a.statement, a.rationale,
65
- d.confidence, d.evidence, d.detected_at
66
- FROM drift_edges d
67
- JOIN drift_anchors a ON a.id = d.anchor_id
68
- WHERE d.verdict = 'absent' AND d.status = 'open' AND a.status = 'active'
69
- ORDER BY a.created_at ASC
64
+ .prepare(`SELECT d.id AS edge_id, d.anchor_id, a.kind, a.statement, a.rationale,
65
+ d.confidence, d.evidence, d.detected_at
66
+ FROM drift_edges d
67
+ JOIN drift_anchors a ON a.id = d.anchor_id
68
+ WHERE d.verdict = 'absent' AND d.status = 'open' AND a.status = 'active'
69
+ ORDER BY a.created_at ASC
70
70
  LIMIT ?`)
71
71
  .all(limit);
72
72
  return rows.map((r) => {
@@ -85,8 +85,8 @@ export function getDriftAbsences(db, opts = {}) {
85
85
  }
86
86
  function countOpen(db, verdict) {
87
87
  return db
88
- .prepare(`SELECT COUNT(*) AS n
89
- FROM drift_edges d JOIN drift_anchors a ON a.id = d.anchor_id
88
+ .prepare(`SELECT COUNT(*) AS n
89
+ FROM drift_edges d JOIN drift_anchors a ON a.id = d.anchor_id
90
90
  WHERE d.verdict = ? AND d.status = 'open' AND a.status = 'active'`)
91
91
  .get(verdict).n;
92
92
  }
@@ -71,13 +71,13 @@ export function importMap(db, map) {
71
71
  const anchorExists = db.prepare('SELECT 1 FROM drift_anchors WHERE id = ?');
72
72
  let linked = 0;
73
73
  const tx = db.transaction(() => {
74
- db.prepare(`
75
- INSERT INTO map_projects (project, job, audience, product_status, template, stages, related_projects, updated_at)
76
- VALUES (@project, @job, @audience, @product_status, @template, @stages, @related_projects, unixepoch())
77
- ON CONFLICT(project) DO UPDATE SET
78
- job=excluded.job, audience=excluded.audience, product_status=excluded.product_status,
79
- template=excluded.template, stages=excluded.stages, related_projects=excluded.related_projects,
80
- updated_at=unixepoch()
74
+ db.prepare(`
75
+ INSERT INTO map_projects (project, job, audience, product_status, template, stages, related_projects, updated_at)
76
+ VALUES (@project, @job, @audience, @product_status, @template, @stages, @related_projects, unixepoch())
77
+ ON CONFLICT(project) DO UPDATE SET
78
+ job=excluded.job, audience=excluded.audience, product_status=excluded.product_status,
79
+ template=excluded.template, stages=excluded.stages, related_projects=excluded.related_projects,
80
+ updated_at=unixepoch()
81
81
  `).run({
82
82
  project: map.project,
83
83
  job: map.job ?? null,
@@ -89,13 +89,13 @@ export function importMap(db, map) {
89
89
  });
90
90
  db.prepare('DELETE FROM map_edges WHERE project = ?').run(map.project);
91
91
  db.prepare('DELETE FROM map_nodes WHERE project = ?').run(map.project);
92
- const insNode = db.prepare(`
93
- INSERT INTO map_nodes
94
- (id, project, layer, stage, statement, status, facets, role, note, due,
95
- paused_reason, related_project, spinout_candidate, anchor_id, review_by, revival_condition, reality, extra, updated_at)
96
- VALUES
97
- (@id, @project, @layer, @stage, @statement, @status, @facets, @role, @note, @due,
98
- @paused_reason, @related_project, @spinout_candidate, @anchor_id, @review_by, @revival_condition, @reality, @extra, unixepoch())
92
+ const insNode = db.prepare(`
93
+ INSERT INTO map_nodes
94
+ (id, project, layer, stage, statement, status, facets, role, note, due,
95
+ paused_reason, related_project, spinout_candidate, anchor_id, review_by, revival_condition, reality, extra, updated_at)
96
+ VALUES
97
+ (@id, @project, @layer, @stage, @statement, @status, @facets, @role, @note, @due,
98
+ @paused_reason, @related_project, @spinout_candidate, @anchor_id, @review_by, @revival_condition, @reality, @extra, unixepoch())
99
99
  `);
100
100
  for (const n of map.nodes) {
101
101
  if (!n.id)
@@ -134,9 +134,9 @@ export function importMap(db, map) {
134
134
  extra: JSON.stringify(extra),
135
135
  });
136
136
  }
137
- const insEdge = db.prepare(`
138
- INSERT OR IGNORE INTO map_edges (project, from_id, to_id, type, strength, note)
139
- VALUES (?, ?, ?, ?, ?, ?)
137
+ const insEdge = db.prepare(`
138
+ INSERT OR IGNORE INTO map_edges (project, from_id, to_id, type, strength, note)
139
+ VALUES (?, ?, ?, ?, ?, ?)
140
140
  `);
141
141
  for (const e of map.edges)
142
142
  insEdge.run(map.project, e.from, e.to, e.type, e.strength ?? null, e.note ?? null);
@@ -0,0 +1,56 @@
1
+ export declare const DRAFT_PAGE = "https://kansei-link.com/agents/note/new.html";
2
+ export declare const URL_MAX = 2000;
3
+ export declare const KINDS: readonly ["execution", "documentation", "hypothesis"];
4
+ export declare const RUNTIMES: readonly ["claude", "codex", "gpt", "gemini", "other"];
5
+ /** Text fields in URL order, with the page's caps (same numbers as the page's maxlength). */
6
+ export declare const TEXT_FIELDS: readonly [{
7
+ readonly key: "task";
8
+ readonly max: 300;
9
+ readonly required: true;
10
+ }, {
11
+ readonly key: "symptom";
12
+ readonly max: 600;
13
+ readonly required: true;
14
+ }, {
15
+ readonly key: "tried";
16
+ readonly max: 2000;
17
+ readonly required: true;
18
+ }, {
19
+ readonly key: "result";
20
+ readonly max: 1000;
21
+ readonly required: true;
22
+ }, {
23
+ readonly key: "conditions";
24
+ readonly max: 600;
25
+ readonly required: false;
26
+ }];
27
+ export declare const ALLOWED_KEYS: readonly ["service", ...("task" | "symptom" | "tried" | "result" | "conditions")[], "kind", "runtime"];
28
+ export declare const SERVICE_RE: RegExp;
29
+ export declare const NOTICE: readonly ["この内容は、開いた画面で確認してから送られます。送るまでどこにも出ていません。", "URL の中身は KanseiLINK の通信の記録(Cloudflare)を通ります。秘密は入れないでください。"];
30
+ export declare const NOTICE_EN: readonly ["Nothing is sent yet: the page opens with these fields filled, and only your click there sends them.", "The URL passes through KanseiLINK's request logs (Cloudflare). Do not put secrets in it."];
31
+ /** Which secret shapes a text contains (kinds, in check order). Empty = clean. */
32
+ export declare function findSecretShapes(text: string): Array<{
33
+ kind: string;
34
+ ja: string;
35
+ }>;
36
+ export interface ShareDraftProblem {
37
+ field: string;
38
+ reason: string;
39
+ kinds?: string[];
40
+ }
41
+ export type ShareDraftResult = {
42
+ ok: true;
43
+ url: string;
44
+ length: number;
45
+ notice: readonly string[];
46
+ notice_en: readonly string[];
47
+ } | {
48
+ ok: false;
49
+ problems: ShareDraftProblem[];
50
+ hint: string;
51
+ };
52
+ /**
53
+ * Check the fields and build the draft URL. Pure: no I/O, no network, no DB.
54
+ * Returns problems (field + reason) instead of a URL when anything is off.
55
+ */
56
+ export declare function buildShareDraft(input: unknown): ShareDraftResult;