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 +3 -0
- package/dist/index.d.ts +8 -5
- package/dist/index.js +179 -31
- package/docs/launch/DEMO_SCRIPT.md +132 -0
- package/docs/launch/LAUNCH_KIT.md +84 -0
- package/docs/launch/POSTS.md +139 -0
- package/package.json +1 -1
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:
|
|
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
|
|
3059
|
-
|
|
3060
|
-
|
|
3058
|
+
function findUnsafeSecretAssignment(line) {
|
|
3059
|
+
const match = line.match(SECRET_ASSIGNMENT_PATTERN);
|
|
3060
|
+
if (!match) {
|
|
3061
|
+
return null;
|
|
3061
3062
|
}
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
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
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
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(
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
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
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
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