snipara-companion 2.0.7 → 2.0.8

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/README.md CHANGED
@@ -99,4 +99,7 @@ The previous long README has moved to [docs/FULL_REFERENCE.md](./docs/FULL_REFER
99
99
  Start there for the full command list, hook setup, hosted MCP bridge commands,
100
100
  workflow modes, team-sync, local context packs, and release-oriented flows.
101
101
 
102
+ Launch assets, demo scripts, and post drafts live in
103
+ [docs/launch/LAUNCH_KIT.md](./docs/launch/LAUNCH_KIT.md).
104
+
102
105
  Release notes live in [CHANGELOG.md](./CHANGELOG.md).
package/dist/index.d.ts CHANGED
@@ -1532,9 +1532,16 @@ interface LocalCodeOverlayImport {
1532
1532
  specifier: string;
1533
1533
  line: number;
1534
1534
  }
1535
+ interface LocalCodeOverlayWarning {
1536
+ code: string;
1537
+ severity: "info" | "warning";
1538
+ message: string;
1539
+ [key: string]: unknown;
1540
+ }
1535
1541
  interface LocalCodeOverlayExcludedFile {
1536
1542
  path: string;
1537
1543
  reason: "ignored" | "unsupported_language" | "too_large" | "secret_pattern" | "read_error";
1544
+ line?: number;
1538
1545
  }
1539
1546
  interface LocalCodeOverlayManifest {
1540
1547
  version: "snipara.local_code_overlay.v1";
@@ -1560,11 +1567,7 @@ interface LocalCodeOverlayManifest {
1560
1567
  byReason: Record<LocalCodeOverlayExcludedFile["reason"], number>;
1561
1568
  samples: LocalCodeOverlayExcludedFile[];
1562
1569
  };
1563
- warnings: Array<{
1564
- code: string;
1565
- severity: "info" | "warning";
1566
- message: string;
1567
- }>;
1570
+ warnings: LocalCodeOverlayWarning[];
1568
1571
  }
1569
1572
  interface CodeStatusCommandOptions {
1570
1573
  dir?: string;
package/dist/index.js CHANGED
@@ -3055,19 +3055,63 @@ function isIgnored(filePath, sniparaIgnore) {
3055
3055
  }
3056
3056
  return sniparaIgnore.some((pattern) => matchesIgnorePattern(normalized, pattern));
3057
3057
  }
3058
- function hasSecretPattern2(content) {
3059
- if (SECRET_PATTERNS.some((pattern) => pattern.test(content))) {
3060
- return true;
3058
+ function findUnsafeSecretAssignment(line) {
3059
+ const match = line.match(SECRET_ASSIGNMENT_PATTERN);
3060
+ if (!match) {
3061
+ return null;
3061
3062
  }
3062
- return content.split(/\r?\n/).some((line) => {
3063
- const match = line.match(SECRET_ASSIGNMENT_PATTERN);
3064
- if (!match) {
3065
- return false;
3066
- }
3067
- const value = match[1] ?? match[2] ?? match[3] ?? "";
3068
- const normalized = value.trim().toLowerCase();
3069
- return !SAFE_SECRET_VALUE_PREFIXES.some((prefix) => normalized.startsWith(prefix));
3063
+ const value = match[1] ?? match[2] ?? match[3] ?? "";
3064
+ const normalized = value.trim().toLowerCase();
3065
+ if (SAFE_SECRET_VALUE_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
3066
+ return null;
3067
+ }
3068
+ return { value };
3069
+ }
3070
+ function redactSecretLikeContentForOverlay(content) {
3071
+ const findings = [];
3072
+ const lines = content.split(/\r?\n/);
3073
+ const redactedLines = lines.map((line, index) => {
3074
+ const lineNumber = index + 1;
3075
+ if (SECRET_PATTERNS.some((pattern) => pattern.test(line))) {
3076
+ findings.push({ line: lineNumber, reason: "private_key" });
3077
+ return "[SNIPARA_REDACTED_SECRET_LIKE_LINE]";
3078
+ }
3079
+ const assignment = findUnsafeSecretAssignment(line);
3080
+ if (!assignment) {
3081
+ return line;
3082
+ }
3083
+ findings.push({ line: lineNumber, reason: "secret_assignment" });
3084
+ return line.replace(assignment.value, "[SNIPARA_REDACTED_SECRET]");
3070
3085
  });
3086
+ return {
3087
+ content: redactedLines.join("\n"),
3088
+ findings
3089
+ };
3090
+ }
3091
+ function formatSecretWarningMessage(samples) {
3092
+ const displayed = samples.slice(0, 5).map((sample) => {
3093
+ const lines = sample.lines.slice(0, 5).join(",");
3094
+ const suffix = sample.lines.length > 5 ? ",..." : "";
3095
+ return `${sample.path}:${lines}${suffix}`;
3096
+ });
3097
+ const more = samples.length > displayed.length ? `; ${samples.length - displayed.length} more` : "";
3098
+ return `Secret-like lines were redacted before local graph extraction; files remain visible to impact. Samples: ${displayed.join("; ")}${more}.`;
3099
+ }
3100
+ function toSecretWarningSamples(samples) {
3101
+ return samples.map((sample) => ({
3102
+ path: sample.path,
3103
+ lines: sample.findings.map((finding) => finding.line),
3104
+ reasons: [...new Set(sample.findings.map((finding) => finding.reason))]
3105
+ }));
3106
+ }
3107
+ function formatSecretRedactionWarning(samples) {
3108
+ const warningSamples = toSecretWarningSamples(samples);
3109
+ return {
3110
+ code: "secret_like_lines_redacted",
3111
+ severity: "warning",
3112
+ message: formatSecretWarningMessage(warningSamples),
3113
+ samples: warningSamples
3114
+ };
3071
3115
  }
3072
3116
  function listWorkingTreeFiles(repoRoot) {
3073
3117
  const files = splitNul(runGitBuffer(["ls-files", "-co", "--exclude-standard", "-z"], repoRoot));
@@ -3229,6 +3273,7 @@ function buildLocalCodeOverlay(options = {}) {
3229
3273
  const symbols = [];
3230
3274
  const imports = [];
3231
3275
  const excludedSamples = [];
3276
+ const secretRedactionSamples = [];
3232
3277
  const byReason = {
3233
3278
  ignored: 0,
3234
3279
  unsupported_language: 0,
@@ -3271,16 +3316,17 @@ function buildLocalCodeOverlay(options = {}) {
3271
3316
  continue;
3272
3317
  }
3273
3318
  const content = contentBuffer.toString("utf8");
3274
- if (hasSecretPattern2(content)) {
3275
- addExcluded(filePath, "secret_pattern");
3276
- continue;
3319
+ const redaction = redactSecretLikeContentForOverlay(content);
3320
+ const indexedContent = redaction.content;
3321
+ if (redaction.findings.length > 0) {
3322
+ secretRedactionSamples.push({ path: filePath, findings: redaction.findings });
3277
3323
  }
3278
- const extracted = extractCode(content, filePath, language);
3324
+ const extracted = extractCode(indexedContent, filePath, language);
3279
3325
  files.push({
3280
3326
  path: filePath,
3281
3327
  language,
3282
3328
  sizeBytes: contentBuffer.length,
3283
- sha256: sha2563(contentBuffer),
3329
+ sha256: sha2563(indexedContent),
3284
3330
  symbolCount: extracted.symbols.length,
3285
3331
  importCount: extracted.imports.length
3286
3332
  });
@@ -3300,12 +3346,8 @@ function buildLocalCodeOverlay(options = {}) {
3300
3346
  message: "The working tree is dirty, but this overlay was built from the selected commit only."
3301
3347
  });
3302
3348
  }
3303
- if (byReason.secret_pattern > 0) {
3304
- warnings.push({
3305
- code: "secret_like_files_excluded",
3306
- severity: "warning",
3307
- message: "One or more supported code files were excluded because they matched secret patterns."
3308
- });
3349
+ if (secretRedactionSamples.length > 0) {
3350
+ warnings.push(formatSecretRedactionWarning(secretRedactionSamples));
3309
3351
  }
3310
3352
  const dirtyTreeHash = mode === "working_tree" && dirtyStatus.trim() ? shortHash(
3311
3353
  JSON.stringify({
@@ -3499,6 +3541,86 @@ function buildLocalFileEdges(manifest) {
3499
3541
  return target ? [{ from: item.filePath, to: target, specifier: item.specifier, line: item.line }] : [];
3500
3542
  });
3501
3543
  }
3544
+ function missingTargetDetail(manifest, filePath) {
3545
+ const excluded = manifest.excluded.samples.find((sample) => sample.path === filePath);
3546
+ if (!excluded) {
3547
+ const hitFileLimit = manifest.warnings.some(
3548
+ (warning) => warning.code === "local_overlay_file_limit_reached"
3549
+ );
3550
+ return {
3551
+ path: filePath,
3552
+ reason: "not_in_overlay",
3553
+ remediation: hitFileLimit ? "The overlay reached --max-files before this target was indexed. Increase --max-files and rebuild without --cached." : "Rebuild without --cached, then check the path, .sniparaignore, supported language, or generated-file state."
3554
+ };
3555
+ }
3556
+ const remediationByReason = {
3557
+ ignored: "Remove or narrow the matching .sniparaignore/default ignore rule if this file should be indexed.",
3558
+ unsupported_language: "Local overlay impact currently indexes TypeScript, TSX, Python, and Go files.",
3559
+ too_large: "This file is above the local overlay size limit. Split the generated file or raise maxFileBytes in the local overlay builder.",
3560
+ secret_pattern: "Rebuild without --cached so secret-like lines can be redacted and the file can stay in the graph; inspect the reported line if it should be changed.",
3561
+ read_error: "The file could not be read from the selected working tree or commit."
3562
+ };
3563
+ return {
3564
+ path: filePath,
3565
+ reason: excluded.reason,
3566
+ ...excluded.line ? { line: excluded.line } : {},
3567
+ remediation: remediationByReason[excluded.reason]
3568
+ };
3569
+ }
3570
+ function buildMissingTargetsWarning(manifest, missingTargetFiles) {
3571
+ if (missingTargetFiles.length === 0) {
3572
+ return null;
3573
+ }
3574
+ const details = missingTargetFiles.map((filePath) => missingTargetDetail(manifest, filePath));
3575
+ const reasons = [...new Set(details.map((detail) => String(detail.reason)))];
3576
+ const remediation = [...new Set(details.map((detail) => String(detail.remediation)))].join(" ");
3577
+ return {
3578
+ code: "local_impact_targets_missing",
3579
+ severity: "warning",
3580
+ message: "One or more requested impact targets are not present in the selected local overlay. " + remediation,
3581
+ files: missingTargetFiles,
3582
+ reasons,
3583
+ details
3584
+ };
3585
+ }
3586
+ function parseSecretWarningSamples(value) {
3587
+ if (!Array.isArray(value)) {
3588
+ return [];
3589
+ }
3590
+ return value.flatMap((item) => {
3591
+ if (!item || typeof item !== "object") {
3592
+ return [];
3593
+ }
3594
+ const record = item;
3595
+ const pathValue = typeof record.path === "string" ? record.path : void 0;
3596
+ const lines = Array.isArray(record.lines) ? record.lines.filter((line) => typeof line === "number") : [];
3597
+ const reasons = Array.isArray(record.reasons) ? record.reasons.filter((reason) => typeof reason === "string") : [];
3598
+ return pathValue && lines.length > 0 ? [{ path: pathValue, lines, reasons }] : [];
3599
+ });
3600
+ }
3601
+ function impactOverlayWarnings(warnings, relevantFiles) {
3602
+ return warnings.flatMap((warning) => {
3603
+ if (warning.code === "local_overlay_file_limit_reached") {
3604
+ return [warning];
3605
+ }
3606
+ if (warning.code !== "secret_like_lines_redacted") {
3607
+ return [];
3608
+ }
3609
+ const samples = parseSecretWarningSamples(warning.samples).filter(
3610
+ (sample) => relevantFiles.has(sample.path)
3611
+ );
3612
+ if (samples.length === 0) {
3613
+ return [];
3614
+ }
3615
+ return [
3616
+ {
3617
+ ...warning,
3618
+ message: formatSecretWarningMessage(samples),
3619
+ samples
3620
+ }
3621
+ ];
3622
+ });
3623
+ }
3502
3624
  function printLocalQueryResult(result, json) {
3503
3625
  if (json) {
3504
3626
  console.log(JSON.stringify(result, null, 2));
@@ -3725,6 +3847,7 @@ function printLocalImpactHumanResult(result) {
3725
3847
  const outgoing = uniqueEdgeFiles(impact.outgoing, "to");
3726
3848
  const warnings = Array.isArray(impact.warnings) ? impact.warnings : [];
3727
3849
  const missingTargets = Array.isArray(impact.missingTargetFiles) ? impact.missingTargetFiles.filter((item) => typeof item === "string") : [];
3850
+ const missingTargetDetails = extractMissingTargetDetails(impact.missingTargetDetails);
3728
3851
  console.log(import_chalk.default.bold(`Code impact - local - ${target}`));
3729
3852
  console.log(`Source: ${result.sourceSelection.selected}`);
3730
3853
  console.log(`Reason: ${result.sourceSelection.reason}`);
@@ -3736,7 +3859,8 @@ function printLocalImpactHumanResult(result) {
3736
3859
  console.log("");
3737
3860
  console.log(import_chalk.default.yellow(`Missing targets (${missingTargets.length})`));
3738
3861
  for (const filePath of missingTargets.slice(0, 12)) {
3739
- console.log(` ${filePath}`);
3862
+ const detail = missingTargetDetails.get(filePath);
3863
+ console.log(` ${filePath}${detail ? ` (${detail})` : ""}`);
3740
3864
  }
3741
3865
  if (missingTargets.length > 12) {
3742
3866
  console.log(` ... ${missingTargets.length - 12} more`);
@@ -3789,6 +3913,26 @@ function uniqueEdgeFiles(value, field) {
3789
3913
  )
3790
3914
  ].sort();
3791
3915
  }
3916
+ function extractMissingTargetDetails(value) {
3917
+ const details = /* @__PURE__ */ new Map();
3918
+ if (!Array.isArray(value)) {
3919
+ return details;
3920
+ }
3921
+ for (const item of value) {
3922
+ if (!item || typeof item !== "object") {
3923
+ continue;
3924
+ }
3925
+ const record = item;
3926
+ const pathValue = typeof record.path === "string" ? record.path : void 0;
3927
+ const reason = typeof record.reason === "string" ? record.reason : void 0;
3928
+ const line = typeof record.line === "number" ? record.line : void 0;
3929
+ if (!pathValue || !reason) {
3930
+ continue;
3931
+ }
3932
+ details.set(pathValue, line ? `excluded: ${reason} at line ${line}` : `excluded: ${reason}`);
3933
+ }
3934
+ return details;
3935
+ }
3792
3936
  function printFileList(title, description, files) {
3793
3937
  const displayLimit = 12;
3794
3938
  console.log(`${title} (${files.length}) - ${description}`);
@@ -4075,14 +4219,17 @@ function buildLocalImpactResult(options) {
4075
4219
  const impactedFiles = [
4076
4220
  .../* @__PURE__ */ new Set([...incoming.map((edge) => edge.from), ...outgoing.map((edge) => edge.to)])
4077
4221
  ].sort();
4078
- const warnings = missingTargetFiles.length > 0 ? [
4079
- {
4080
- code: "local_impact_targets_missing",
4081
- severity: "warning",
4082
- message: "One or more requested impact targets are not present in the selected local overlay. Rebuild without --cached or rerun code sync with a larger --max-files value.",
4083
- files: missingTargetFiles
4084
- }
4085
- ] : [];
4222
+ const relevantWarningFiles = /* @__PURE__ */ new Set([
4223
+ ...selectedFiles,
4224
+ ...impactedFiles,
4225
+ ...incoming.map((edge) => edge.from),
4226
+ ...outgoing.map((edge) => edge.to)
4227
+ ]);
4228
+ const missingTargetsWarning = buildMissingTargetsWarning(manifest, missingTargetFiles);
4229
+ const warnings = [
4230
+ ...missingTargetsWarning ? [missingTargetsWarning] : [],
4231
+ ...impactOverlayWarnings(manifest.warnings, relevantWarningFiles)
4232
+ ];
4086
4233
  return {
4087
4234
  title: "Local impact",
4088
4235
  caveat: "Local impact reports file-level import neighbors from the current checkout. Use --source hosted only when you want the hosted team graph.",
@@ -4090,6 +4237,7 @@ function buildLocalImpactResult(options) {
4090
4237
  target: symbol ? compactSymbol(symbol) : { changedFiles, missingTargetFiles },
4091
4238
  changedFiles,
4092
4239
  missingTargetFiles,
4240
+ missingTargetDetails: missingTargetsWarning?.details ?? [],
4093
4241
  warnings,
4094
4242
  symbols: manifest.symbols.filter((item) => selectedFiles.has(item.filePath)).map(compactSymbol),
4095
4243
  incoming,
@@ -0,0 +1,132 @@
1
+ # Demo Script
2
+
3
+ Goal: produce a 15 to 30 second terminal recording that proves the first-run
4
+ promise without depending on a private repo.
5
+
6
+ ## Recording Setup
7
+
8
+ - Terminal size: 88 x 26 or similar.
9
+ - Theme: high contrast, readable font, no prompt plugins that leak paths or
10
+ credentials.
11
+ - Network is needed only for `npx` to fetch the package. The analysis command
12
+ uses `--source local`.
13
+ - Do not record private repositories, environment variables, or secrets.
14
+
15
+ ## Deterministic Demo Repo
16
+
17
+ Paste this into a clean shell:
18
+
19
+ ```bash
20
+ DEMO_DIR="$(mktemp -d)"
21
+ cd "$DEMO_DIR"
22
+ git init >/dev/null
23
+ git config user.email demo@example.com
24
+ git config user.name Demo
25
+
26
+ mkdir -p src/auth apps/web/src/lib/auth apps/web/src/app/api/auth/session
27
+
28
+ cat > src/auth/cookies.ts <<'EOF'
29
+ export function readCookie(name: string) {
30
+ return name;
31
+ }
32
+ EOF
33
+
34
+ cat > src/auth/tokens.ts <<'EOF'
35
+ export function verifyToken(token: string) {
36
+ return token.length > 0;
37
+ }
38
+ EOF
39
+
40
+ cat > src/auth/session.ts <<'EOF'
41
+ import { readCookie } from './cookies';
42
+ import { verifyToken } from './tokens';
43
+
44
+ export function readSession() {
45
+ return verifyToken(readCookie('session'));
46
+ }
47
+ EOF
48
+
49
+ cat > apps/web/src/lib/auth/permissions.ts <<'EOF'
50
+ import { readSession } from '../../../../src/auth/session';
51
+
52
+ export function canReadAdmin() {
53
+ return readSession();
54
+ }
55
+ EOF
56
+
57
+ cat > apps/web/src/app/api/auth/session/route.ts <<'EOF'
58
+ import { readSession } from '../../../../../../src/auth/session';
59
+
60
+ export function GET() {
61
+ return readSession();
62
+ }
63
+ EOF
64
+
65
+ git add .
66
+ git commit -m "demo repo" >/dev/null
67
+
68
+ npx -y snipara-companion impact src/auth/session.ts --source local
69
+ ```
70
+
71
+ Expected output shape:
72
+
73
+ ```text
74
+ Code impact - local - src/auth/session.ts
75
+ Source: local_overlay
76
+ Reason: source_forced_local
77
+
78
+ Incoming (2) - files that depend on this
79
+ apps/web/src/app/api/auth/session/route.ts
80
+ apps/web/src/lib/auth/permissions.ts
81
+
82
+ Outgoing (2) - files this depends on
83
+ src/auth/cookies.ts
84
+ src/auth/tokens.ts
85
+
86
+ Use --json for full overlay details.
87
+ ```
88
+
89
+ ## Asciinema
90
+
91
+ ```bash
92
+ asciinema rec docs/launch/impact.cast
93
+ # run the deterministic demo commands
94
+ asciinema play docs/launch/impact.cast
95
+ ```
96
+
97
+ Optional GIF conversion if `agg` is installed:
98
+
99
+ ```bash
100
+ agg docs/launch/impact.cast docs/launch/impact.gif
101
+ ```
102
+
103
+ ## VHS
104
+
105
+ Create `docs/launch/impact.tape`:
106
+
107
+ ```text
108
+ Output docs/launch/impact.gif
109
+ Set FontSize 18
110
+ Set Width 1200
111
+ Set Height 720
112
+ Set TypingSpeed 50ms
113
+
114
+ Type "npx -y snipara-companion impact src/auth/session.ts --source local"
115
+ Enter
116
+ Sleep 3s
117
+ ```
118
+
119
+ Then run:
120
+
121
+ ```bash
122
+ vhs docs/launch/impact.tape
123
+ ```
124
+
125
+ ## Review Checklist
126
+
127
+ - The command appears in the first five seconds.
128
+ - The words `Incoming` and `Outgoing` are visible without scrolling.
129
+ - There is no login, browser, account setup, or `init`.
130
+ - No private path, token, secret, or customer name appears.
131
+ - If the output is empty or surprising, fix the CLI or use the issue template
132
+ before posting the demo.
@@ -0,0 +1,84 @@
1
+ # snipara-companion Launch Kit
2
+
3
+ Use this when announcing `snipara-companion` or preparing a demo.
4
+
5
+ ## Positioning
6
+
7
+ One-liner:
8
+
9
+ > Ask your repo what breaks if you touch a file.
10
+
11
+ Problem:
12
+
13
+ AI coding agents often edit before they understand local blast radius, and long
14
+ agent work loses continuity across compaction, handoffs, and cold starts.
15
+
16
+ First-run command:
17
+
18
+ ```bash
19
+ npx -y snipara-companion impact src/auth/session.ts --source local
20
+ ```
21
+
22
+ Expected shape:
23
+
24
+ ```text
25
+ Code impact - local - src/auth/session.ts
26
+ Source: local_overlay
27
+ Reason: source_forced_local
28
+
29
+ Incoming (2) - files that depend on this
30
+ apps/web/src/lib/auth/permissions.ts
31
+ apps/web/src/app/api/auth/session/route.ts
32
+
33
+ Outgoing (2) - files this depends on
34
+ src/auth/cookies.ts
35
+ src/auth/tokens.ts
36
+
37
+ Use --json for full overlay details.
38
+ ```
39
+
40
+ ## What To Say
41
+
42
+ - No global install, no init, no account for the local first run.
43
+ - `impact` builds a local file-level code overlay from the current checkout.
44
+ - Local mode is first-class for one repo, one machine, and one session.
45
+ - Hosted Snipara is optional for team memory, semantic retrieval, cross-machine
46
+ presence, and outcome learning.
47
+ - Agent continuity is the second hook: `.snipara/` keeps phase state, handoffs,
48
+ context packs, and verification breadcrumbs durable.
49
+
50
+ ## What Not To Say
51
+
52
+ - Do not claim perfect AST call-site analysis. Local impact is file-level import
53
+ impact.
54
+ - Do not imply hosted Snipara is required for the first run.
55
+ - Do not say code is uploaded by default. `--source local` stays local.
56
+ - Do not present local mode as degraded. It is complete for local repo impact
57
+ and workflow continuity.
58
+
59
+ ## Launch Checklist
60
+
61
+ - Verify the published package:
62
+
63
+ ```bash
64
+ npm view snipara-companion version bin dist-tags --json
65
+ npx -y snipara-companion@latest --version
66
+ npx -y snipara-companion@latest impact src/index.ts --source local
67
+ ```
68
+
69
+ - Record a 15 to 30 second terminal demo using
70
+ [DEMO_SCRIPT.md](./DEMO_SCRIPT.md).
71
+ - Publish the GitHub README link and npm link together.
72
+ - Post one problem-first version from [POSTS.md](./POSTS.md), not every channel
73
+ at once.
74
+ - Watch GitHub issues for the first 48 hours and label:
75
+ `impact-feedback`, `docs-feedback`, `good-first-issue`, `privacy`, `bug`.
76
+ - Turn repeated confusion into README edits before adding new features.
77
+
78
+ ## Primary Links
79
+
80
+ - GitHub: <https://github.com/Snipara/snipara-companion>
81
+ - npm: <https://www.npmjs.com/package/snipara-companion>
82
+ - Full reference: [../FULL_REFERENCE.md](../FULL_REFERENCE.md)
83
+ - Demo script: [DEMO_SCRIPT.md](./DEMO_SCRIPT.md)
84
+ - Post drafts: [POSTS.md](./POSTS.md)
@@ -0,0 +1,139 @@
1
+ # Post Drafts
2
+
3
+ Keep the message problem-first. Edit names, links, and examples before posting.
4
+
5
+ ## Short Social
6
+
7
+ I built `snipara-companion` for the moment before an AI coding agent edits a
8
+ file:
9
+
10
+ ```bash
11
+ npx -y snipara-companion impact src/auth/session.ts --source local
12
+ ```
13
+
14
+ No install, no init, no account. It asks your current checkout what depends on
15
+ that file and what it imports. Local mode stays on your machine.
16
+
17
+ GitHub: https://github.com/Snipara/snipara-companion
18
+
19
+ ## Hacker News
20
+
21
+ Title:
22
+
23
+ ```text
24
+ Show HN: Ask your repo what breaks if you touch a file
25
+ ```
26
+
27
+ Body:
28
+
29
+ ```text
30
+ I built snipara-companion, a small local CLI for AI coding sessions.
31
+
32
+ The first command is:
33
+
34
+ npx -y snipara-companion impact src/auth/session.ts --source local
35
+
36
+ It builds a local file-level code overlay from the current checkout and prints
37
+ Incoming and Outgoing files, so an agent or developer can see the local blast
38
+ radius before editing. No account, init, or hosted graph is required for that
39
+ path.
40
+
41
+ The second use case is agent continuity: phase state, handoffs, context packs,
42
+ and verification breadcrumbs are written under .snipara/ so a later agent can
43
+ resume without relying on chat history.
44
+
45
+ It is not a perfect call-site analyzer. The local graph is import/file-level by
46
+ design. Hosted Snipara is optional for team memory, semantic retrieval, and
47
+ cross-project context.
48
+
49
+ GitHub: https://github.com/Snipara/snipara-companion
50
+ npm: https://www.npmjs.com/package/snipara-companion
51
+ ```
52
+
53
+ ## Reddit
54
+
55
+ Suggested communities: `r/opensource`, `r/programming`, or a tool-specific
56
+ community where self-promotion is allowed.
57
+
58
+ ```text
59
+ I built a local CLI for the first minute of an AI coding session:
60
+
61
+ npx -y snipara-companion impact src/auth/session.ts --source local
62
+
63
+ It prints the files that depend on the target and the files it imports, using a
64
+ local file-level overlay from the current checkout. No account or hosted graph
65
+ is required for that path.
66
+
67
+ The other half is agent continuity: local .snipara/ workflow state, handoffs,
68
+ context packs, and verification notes so work can survive compaction or handoff.
69
+
70
+ I would especially like feedback on false positives/negatives in `impact`
71
+ output across real repos.
72
+
73
+ GitHub: https://github.com/Snipara/snipara-companion
74
+ ```
75
+
76
+ ## Product Hunt
77
+
78
+ Tagline:
79
+
80
+ ```text
81
+ Ask your repo what breaks before an AI agent edits it.
82
+ ```
83
+
84
+ Description:
85
+
86
+ ```text
87
+ snipara-companion is a local-first CLI for AI coding sessions. Run one npx
88
+ command to see file-level impact from your current checkout, then keep longer
89
+ agent work resumable with local workflow state, handoffs, and context packs.
90
+ No account is needed for the local path.
91
+ ```
92
+
93
+ ## LinkedIn
94
+
95
+ ```text
96
+ The riskiest part of many AI coding sessions is the first minute: the agent
97
+ starts editing before it understands local blast radius.
98
+
99
+ We made the first command in snipara-companion:
100
+
101
+ npx -y snipara-companion impact src/auth/session.ts --source local
102
+
103
+ It builds a local file-level overlay from the current checkout and shows:
104
+
105
+ - Incoming files that depend on the target
106
+ - Outgoing files the target depends on
107
+ - No account or hosted graph required for the local path
108
+
109
+ The second piece is continuity: .snipara/ phase state, handoffs, context packs,
110
+ and verification breadcrumbs so agent work can survive compaction and handoff.
111
+
112
+ GitHub: https://github.com/Snipara/snipara-companion
113
+ ```
114
+
115
+ ## Maintainer Reply Snippets
116
+
117
+ When someone asks whether it uploads code:
118
+
119
+ ```text
120
+ The `--source local` path does not upload code. It builds a local overlay from
121
+ the current checkout. Hosted Snipara is opt-in for team and cross-project
122
+ features.
123
+ ```
124
+
125
+ When someone expects call-site precision:
126
+
127
+ ```text
128
+ The local overlay is intentionally file-level import impact, not a full
129
+ call-site AST graph. That keeps the first run fast and account-free. Please
130
+ open an issue if the file-level result is wrong or misleading for your repo.
131
+ ```
132
+
133
+ When someone gets empty impact output:
134
+
135
+ ```text
136
+ Please open an "Impact output is wrong or surprising" issue with the command,
137
+ package version, language, and redacted output. The most useful repro is a tiny
138
+ public repo or file pair that shows the missing edge.
139
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snipara-companion",
3
- "version": "2.0.7",
3
+ "version": "2.0.8",
4
4
  "description": "Snipara Git-style companion CLI for local workflow continuity, hooks, hosted context, and Mini Snipara OSS workflows",
5
5
  "main": "dist/index.js",
6
6
  "bin": {