create-snipara 2.0.8 → 2.0.10
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 +2 -1
- package/dist/index.js +443 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ Use it for:
|
|
|
26
26
|
- free account signup with reviewed Context + Memory
|
|
27
27
|
- local project wiring, `doctor`, `repair`, and `upgrade`
|
|
28
28
|
- local source activation without GitHub, plus optional GitHub repository sync and PR Answer Packs approval when the current folder is a GitHub repo
|
|
29
|
+
- day-zero Project Intelligence proof: a First Work Brief, a ready-to-run `snipara_context_query`, a small hosted README/docs seed sync for virgin projects, review-only decision archaeology candidates, possible doc-staleness findings, and an estimated context economy report from the user's own repository
|
|
29
30
|
|
|
30
31
|
## Why It Exists
|
|
31
32
|
|
|
@@ -114,7 +115,7 @@ That distinction matters:
|
|
|
114
115
|
- installs optional `snipara-sandbox`
|
|
115
116
|
- installs optional `snipara-orchestrator` for proof gates, drift checks, and htask workflows when requested
|
|
116
117
|
- configures `.mcp.json`
|
|
117
|
-
- generates merge-ready AI agent templates in `.snipara/templates/`, including
|
|
118
|
+
- generates merge-ready AI agent templates in `.snipara/templates/`, including symbol-card, code-impact, and managed workflow guidance within plan capacity
|
|
118
119
|
- creates `AGENTS.md` and `CLAUDE.md` when missing, or appends a marked Snipara section when those files already exist
|
|
119
120
|
- creates `.cursor/rules/snipara.mdc` and `.codex/config.toml` when those files do not already exist
|
|
120
121
|
- validates Codex config against Hosted MCP plus `bearer_token_env_var = "SNIPARA_API_KEY"` and tells users to restart Codex after MCP config changes
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { homedir } from "os";
|
|
|
13
13
|
// package.json
|
|
14
14
|
var package_default = {
|
|
15
15
|
name: "create-snipara",
|
|
16
|
-
version: "2.0.
|
|
16
|
+
version: "2.0.10",
|
|
17
17
|
description: "Onboard Snipara Hosted MCP Context + Memory with auth, companion workflows, and optional Snipara Sandbox",
|
|
18
18
|
main: "dist/index.js",
|
|
19
19
|
type: "module",
|
|
@@ -104,6 +104,9 @@ var SANDBOX_COMMAND_NAME = "snipara-sandbox";
|
|
|
104
104
|
var LEGACY_SANDBOX_COMMAND_NAME = "rlm";
|
|
105
105
|
var ORCHESTRATOR_COMMAND_NAME = "snipara-orchestrator";
|
|
106
106
|
var DEVICE_CLIENT_ID = "create-snipara";
|
|
107
|
+
var DAY_ZERO_CORPUS_MAX_FILES = 8;
|
|
108
|
+
var DAY_ZERO_CORPUS_MAX_FILE_CHARS = 12e3;
|
|
109
|
+
var DAY_ZERO_CORPUS_MAX_TOTAL_CHARS = 5e4;
|
|
107
110
|
var EXPERIENCE_GOAL_SECTION = `## Experience Goal
|
|
108
111
|
|
|
109
112
|
The target experience is outcome-first agent work across supported clients:
|
|
@@ -115,6 +118,19 @@ deployment, and production smoke checks behind the scenes. Surface important
|
|
|
115
118
|
decisions, blockers, and evidence, but do not make the user manually orchestrate
|
|
116
119
|
routine Snipara or release mechanics.`;
|
|
117
120
|
var UNIVERSAL_CLIENT_GUIDANCE = "The target experience is outcome-first: the user can ask for engineering judgment or a release, and the agent should use Snipara context, workflow state, verification, package checks, Git, deploy, and smoke checks without making the user orchestrate routine mechanics.";
|
|
121
|
+
var DECISION_COMMIT_TERMS = [
|
|
122
|
+
"adopt",
|
|
123
|
+
"because",
|
|
124
|
+
"decide",
|
|
125
|
+
"decision",
|
|
126
|
+
"drop",
|
|
127
|
+
"migrate",
|
|
128
|
+
"prefer",
|
|
129
|
+
"replace",
|
|
130
|
+
"revert",
|
|
131
|
+
"standardize",
|
|
132
|
+
"switch"
|
|
133
|
+
];
|
|
118
134
|
function buildActivationId() {
|
|
119
135
|
return `act_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
120
136
|
}
|
|
@@ -200,23 +216,196 @@ function readRecentCommitSubjects() {
|
|
|
200
216
|
return [];
|
|
201
217
|
}
|
|
202
218
|
}
|
|
219
|
+
function readDecisionCommitSubjectLines() {
|
|
220
|
+
try {
|
|
221
|
+
const output = execSync("git log --pretty=format:%h%x20%s --max-count=50", {
|
|
222
|
+
cwd: process.cwd(),
|
|
223
|
+
encoding: "utf-8",
|
|
224
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
225
|
+
timeout: 1500
|
|
226
|
+
}).trim();
|
|
227
|
+
if (!output) {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
return output.split(/\r?\n/).map((line) => line.trim()).filter((line) => DECISION_COMMIT_TERMS.some((term) => line.toLowerCase().includes(term))).slice(0, 8);
|
|
231
|
+
} catch {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
203
235
|
function readMemoryCandidateLines(relativePath) {
|
|
204
236
|
const absolutePath = path.join(process.cwd(), relativePath);
|
|
205
237
|
if (!fs.existsSync(absolutePath) || fs.statSync(absolutePath).isDirectory()) {
|
|
206
238
|
return [];
|
|
207
239
|
}
|
|
208
240
|
const content = fs.readFileSync(absolutePath, "utf8").slice(0, 24e3);
|
|
209
|
-
return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => /decision|decided|must|should|always|never|convention|rule/i.test(line)).slice(0,
|
|
241
|
+
return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => /decision|decided|must|should|always|never|convention|rule/i.test(line)).slice(0, 4).map((line) => ({
|
|
242
|
+
source: relativePath,
|
|
243
|
+
text: line.slice(0, 280),
|
|
244
|
+
status: "needs_review",
|
|
245
|
+
reason: "Detected project guidance language. Review before storing as durable memory.",
|
|
246
|
+
evidence: classifyDecisionSource(relativePath)
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
function estimateFileTokens(relativePath) {
|
|
250
|
+
const absolutePath = path.join(process.cwd(), relativePath);
|
|
251
|
+
if (!fs.existsSync(absolutePath) || fs.statSync(absolutePath).isDirectory()) {
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const content = fs.readFileSync(absolutePath, "utf8");
|
|
256
|
+
return Math.ceil(content.length / 4);
|
|
257
|
+
} catch {
|
|
258
|
+
return 0;
|
|
259
|
+
}
|
|
210
260
|
}
|
|
211
261
|
function buildMemoryCandidatePreviews(scan) {
|
|
212
|
-
|
|
213
|
-
|
|
262
|
+
const seen = /* @__PURE__ */ new Set();
|
|
263
|
+
const gitCandidates = scan.git.recentDecisionCommits.map((line) => ({
|
|
264
|
+
source: "git log",
|
|
265
|
+
text: line.slice(0, 280),
|
|
266
|
+
status: "needs_review",
|
|
267
|
+
reason: "Detected decision-language in recent commit history. Confirm intent before storing.",
|
|
268
|
+
evidence: "git_commit_subject"
|
|
269
|
+
}));
|
|
270
|
+
const candidates = [
|
|
271
|
+
...scan.files.candidateDocs.flatMap(readMemoryCandidateLines),
|
|
272
|
+
...gitCandidates
|
|
273
|
+
];
|
|
274
|
+
return candidates.filter((candidate) => {
|
|
275
|
+
const key = `${candidate.source}:${candidate.text}`;
|
|
276
|
+
if (seen.has(key)) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
seen.add(key);
|
|
280
|
+
return true;
|
|
281
|
+
}).slice(0, 20);
|
|
282
|
+
}
|
|
283
|
+
function readTextFileSnippet(relativePath, maxChars = 24e3) {
|
|
284
|
+
const absolutePath = path.join(process.cwd(), relativePath);
|
|
285
|
+
if (!fs.existsSync(absolutePath) || fs.statSync(absolutePath).isDirectory()) {
|
|
286
|
+
return "";
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
return fs.readFileSync(absolutePath, "utf8").slice(0, maxChars);
|
|
290
|
+
} catch {
|
|
291
|
+
return "";
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function collectDayZeroCorpusDocuments(scan) {
|
|
295
|
+
const documents = [];
|
|
296
|
+
let totalChars = 0;
|
|
297
|
+
const cwd = process.cwd();
|
|
298
|
+
const rootReadmes = fs.existsSync(cwd) ? fs.readdirSync(cwd).filter((entry) => /^readme(?:\.[a-z0-9_-]+)?\.mdx?$/i.test(entry)).sort() : [];
|
|
299
|
+
const sources = [
|
|
300
|
+
...rootReadmes,
|
|
301
|
+
...listFilesUnder("docs", DAY_ZERO_CORPUS_MAX_FILES * 8),
|
|
302
|
+
...scan.files.candidateDocs
|
|
303
|
+
];
|
|
304
|
+
const seen = /* @__PURE__ */ new Set();
|
|
305
|
+
for (const source of sources) {
|
|
306
|
+
const normalized = source.replace(/\\/g, "/");
|
|
307
|
+
if (seen.has(normalized)) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
seen.add(normalized);
|
|
311
|
+
const lower = normalized.toLowerCase();
|
|
312
|
+
const isReadme = /^readme(?:\.[a-z0-9_-]+)?\.mdx?$/.test(lower);
|
|
313
|
+
const isDocsMarkdown = lower.startsWith("docs/") && /\.(?:md|mdx|markdown|txt|rst|adoc)$/.test(lower);
|
|
314
|
+
if (!isReadme && !isDocsMarkdown) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (documents.length >= DAY_ZERO_CORPUS_MAX_FILES) {
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
if (totalChars >= DAY_ZERO_CORPUS_MAX_TOTAL_CHARS) {
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
const remainingChars = DAY_ZERO_CORPUS_MAX_TOTAL_CHARS - totalChars;
|
|
324
|
+
const content = readTextFileSnippet(
|
|
214
325
|
source,
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
326
|
+
Math.min(DAY_ZERO_CORPUS_MAX_FILE_CHARS, remainingChars)
|
|
327
|
+
).trim();
|
|
328
|
+
if (!content) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
documents.push({
|
|
332
|
+
path: normalized,
|
|
333
|
+
content
|
|
334
|
+
});
|
|
335
|
+
totalChars += content.length;
|
|
336
|
+
}
|
|
337
|
+
return documents;
|
|
338
|
+
}
|
|
339
|
+
function createDayZeroCorpusReport(status, documents, detail, options = {}) {
|
|
340
|
+
return {
|
|
341
|
+
status,
|
|
342
|
+
documents: documents.map((document) => ({
|
|
343
|
+
path: document.path,
|
|
344
|
+
chars: document.content.length
|
|
345
|
+
})),
|
|
346
|
+
detail,
|
|
347
|
+
...options.tool ? { tool: options.tool } : {},
|
|
348
|
+
...options.error ? { error: options.error.slice(0, 500) } : {}
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function buildStalenessFindings(scan) {
|
|
352
|
+
const findings = [];
|
|
353
|
+
const scriptSet = new Set(scan.scripts);
|
|
354
|
+
for (const source of scan.files.candidateDocs.slice(0, 30)) {
|
|
355
|
+
const content = readTextFileSnippet(source);
|
|
356
|
+
if (!content) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const scriptMatches = [...content.matchAll(/\b(?:npm|pnpm|yarn|bun)\s+run\s+([a-z0-9:_-]+)/gi)];
|
|
360
|
+
for (const match of scriptMatches) {
|
|
361
|
+
const scriptName = match[1];
|
|
362
|
+
if (scriptName && !scriptSet.has(scriptName)) {
|
|
363
|
+
findings.push({
|
|
364
|
+
source,
|
|
365
|
+
severity: "possible",
|
|
366
|
+
summary: `Documentation references \`${match[0]}\`, but package.json has no \`${scriptName}\` script.`,
|
|
367
|
+
recommendation: "Verify the setup docs or add the missing script before relying on this instruction."
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (scan.packageManagers.length === 1 && scan.packageManagers[0] === "pnpm" && /\bnpm install\b/i.test(content)) {
|
|
372
|
+
findings.push({
|
|
373
|
+
source,
|
|
374
|
+
severity: "possible",
|
|
375
|
+
summary: "Documentation mentions `npm install`, but the repo has a pnpm lockfile.",
|
|
376
|
+
recommendation: "Confirm whether npm remains supported or update setup docs to pnpm."
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return findings.slice(0, 8);
|
|
381
|
+
}
|
|
382
|
+
function buildContextEconomyReport(scan, dayZeroQuery) {
|
|
383
|
+
const indexedSourceTokens = scan.files.candidateDocs.reduce(
|
|
384
|
+
(total, source) => total + estimateFileTokens(source),
|
|
385
|
+
0
|
|
386
|
+
);
|
|
387
|
+
const rawWindowBaselineTokens = Math.max(indexedSourceTokens, 8e3);
|
|
388
|
+
const estimatedPromptTokens = Math.ceil(dayZeroQuery.length / 4);
|
|
389
|
+
const estimatedRetrievedTokens = Math.min(
|
|
390
|
+
rawWindowBaselineTokens,
|
|
391
|
+
Math.max(estimatedPromptTokens + scan.files.candidateDocs.length * 160, 1200)
|
|
392
|
+
);
|
|
393
|
+
const estimatedSavedTokens = Math.max(0, rawWindowBaselineTokens - estimatedRetrievedTokens);
|
|
394
|
+
return {
|
|
395
|
+
basis: "activation_estimate",
|
|
396
|
+
rawWindowBaselineTokens,
|
|
397
|
+
estimatedRetrievedTokens,
|
|
398
|
+
estimatedSavedTokens,
|
|
399
|
+
savingsPercent: rawWindowBaselineTokens > 0 ? Math.round(estimatedSavedTokens / rawWindowBaselineTokens * 100) : 0,
|
|
400
|
+
note: "Estimated from activation sources until hosted query receipts provide measured savings."
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function buildAgentReadinessReport(scan) {
|
|
404
|
+
const findings = buildStalenessFindings(scan);
|
|
405
|
+
return {
|
|
406
|
+
stalenessFindings: findings,
|
|
407
|
+
summary: findings.length > 0 ? `${findings.length} possible doc staleness finding(s) need review.` : "No obvious setup-doc staleness found in activation sources."
|
|
408
|
+
};
|
|
220
409
|
}
|
|
221
410
|
function writeMemoryCandidates(candidates) {
|
|
222
411
|
fs.mkdirSync(getActivationDir(), { recursive: true });
|
|
@@ -261,15 +450,7 @@ function writeInitialHandoff(answers, manifest) {
|
|
|
261
450
|
function detectLocalActivationScan(detection) {
|
|
262
451
|
const cwd = process.cwd();
|
|
263
452
|
const exists = (relativePath) => fs.existsSync(path.join(cwd, relativePath));
|
|
264
|
-
const candidateDocs =
|
|
265
|
-
"README.md",
|
|
266
|
-
"AGENTS.md",
|
|
267
|
-
"CLAUDE.md",
|
|
268
|
-
"CHANGELOG.md",
|
|
269
|
-
"docs",
|
|
270
|
-
".cursor/rules",
|
|
271
|
-
".github"
|
|
272
|
-
].filter(exists);
|
|
453
|
+
const candidateDocs = findDecisionArchaeologySources(cwd);
|
|
273
454
|
const lockfiles = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"].filter(
|
|
274
455
|
exists
|
|
275
456
|
);
|
|
@@ -305,15 +486,76 @@ function detectLocalActivationScan(detection) {
|
|
|
305
486
|
git: {
|
|
306
487
|
detected: detection.inGitRepo,
|
|
307
488
|
branch: detection.inGitRepo ? detectGitBranch() : null,
|
|
308
|
-
remote: detection.inGitRepo ? readGitRemote() : null
|
|
489
|
+
remote: detection.inGitRepo ? readGitRemote() : null,
|
|
490
|
+
recentDecisionCommits: detection.inGitRepo ? readDecisionCommitSubjectLines() : []
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
function classifyDecisionSource(relativePath) {
|
|
495
|
+
if (/(^|\/)(adr|decisions?)(\/|$)/i.test(relativePath)) {
|
|
496
|
+
return "adr_or_decision_doc";
|
|
497
|
+
}
|
|
498
|
+
if (/AGENTS\.md|CLAUDE\.md|MEMORY\.md|\.cursor\/rules|copilot-instructions/i.test(relativePath)) {
|
|
499
|
+
return "agent_instruction";
|
|
500
|
+
}
|
|
501
|
+
if (/CHANGELOG/i.test(relativePath)) {
|
|
502
|
+
return "changelog";
|
|
503
|
+
}
|
|
504
|
+
return "project_document";
|
|
505
|
+
}
|
|
506
|
+
function listFilesUnder(relativeDir, maxFiles) {
|
|
507
|
+
const root = path.join(process.cwd(), relativeDir);
|
|
508
|
+
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
511
|
+
const results = [];
|
|
512
|
+
const visit = (dir) => {
|
|
513
|
+
if (results.length >= maxFiles) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
for (const entry of fs.readdirSync(dir).sort()) {
|
|
517
|
+
const absolutePath = path.join(dir, entry);
|
|
518
|
+
const stat = fs.statSync(absolutePath);
|
|
519
|
+
if (stat.isDirectory()) {
|
|
520
|
+
visit(absolutePath);
|
|
521
|
+
} else if (/\.(md|mdx|txt|mdc)$/i.test(entry)) {
|
|
522
|
+
results.push(path.relative(process.cwd(), absolutePath));
|
|
523
|
+
}
|
|
524
|
+
if (results.length >= maxFiles) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
309
527
|
}
|
|
310
528
|
};
|
|
529
|
+
visit(root);
|
|
530
|
+
return results;
|
|
531
|
+
}
|
|
532
|
+
function findDecisionArchaeologySources(cwd) {
|
|
533
|
+
const exists = (relativePath) => fs.existsSync(path.join(cwd, relativePath));
|
|
534
|
+
const directSources = [
|
|
535
|
+
"README.md",
|
|
536
|
+
"CHANGELOG.md",
|
|
537
|
+
"MEMORY.md",
|
|
538
|
+
...["AGENTS.md", "CLAUDE.md", ".github/copilot-instructions.md"].filter(
|
|
539
|
+
(source) => exists(source)
|
|
540
|
+
)
|
|
541
|
+
].filter(exists);
|
|
542
|
+
const archaeologySources = [
|
|
543
|
+
...listFilesUnder("docs/adr", 20),
|
|
544
|
+
...listFilesUnder("docs/adrs", 20),
|
|
545
|
+
...listFilesUnder("docs/decision", 20),
|
|
546
|
+
...listFilesUnder("docs/decisions", 20),
|
|
547
|
+
...listFilesUnder(".cursor/rules", 20)
|
|
548
|
+
];
|
|
549
|
+
return [.../* @__PURE__ */ new Set([...directSources, ...archaeologySources])].slice(0, 80);
|
|
311
550
|
}
|
|
312
551
|
function createInitialFirstWorkBrief(answers, detection, scan) {
|
|
313
552
|
const githubLine = answers.githubRepo ? `GitHub detected: ${answers.githubRepo.fullName}` : "No GitHub integration required for first value.";
|
|
314
553
|
const commands = scan.scripts.length > 0 ? scan.scripts.join(", ") : "no package scripts detected";
|
|
315
554
|
const docs = scan.files.candidateDocs.length > 0 ? scan.files.candidateDocs.join(", ") : "no standard docs detected";
|
|
316
555
|
const languages = scan.languages.length > 0 ? scan.languages.join(", ") : "not detected yet";
|
|
556
|
+
const hotFiles = scan.files.candidateDocs.slice(0, 5);
|
|
557
|
+
const hotFileLine = hotFiles.length > 0 ? `First files to ground on: ${hotFiles.join(", ")}.` : "First grounding files will appear after local source sync or hosted provider sync.";
|
|
558
|
+
const dayZeroQuery = "What is this project, what is currently in flight, and what should an agent know before making the first edit?";
|
|
317
559
|
return {
|
|
318
560
|
title: `First Work Brief for ${answers.projectSlug}`,
|
|
319
561
|
status: "partial",
|
|
@@ -332,9 +574,17 @@ function createInitialFirstWorkBrief(answers, detection, scan) {
|
|
|
332
574
|
lines: [
|
|
333
575
|
`Initial docs: ${docs}.`,
|
|
334
576
|
`Package scripts: ${commands}.`,
|
|
577
|
+
hotFileLine,
|
|
335
578
|
"Source indexing runs before claiming architecture certainty."
|
|
336
579
|
]
|
|
337
580
|
},
|
|
581
|
+
{
|
|
582
|
+
title: "Day-zero answer pack",
|
|
583
|
+
lines: [
|
|
584
|
+
`Ask Snipara: "${dayZeroQuery}"`,
|
|
585
|
+
"Goal: reach one correct, non-obvious statement about this repository in under 5 minutes."
|
|
586
|
+
]
|
|
587
|
+
},
|
|
338
588
|
{
|
|
339
589
|
title: "Current risks",
|
|
340
590
|
lines: [
|
|
@@ -349,22 +599,40 @@ function createInitialFirstWorkBrief(answers, detection, scan) {
|
|
|
349
599
|
{
|
|
350
600
|
title: "Top impacted modules",
|
|
351
601
|
lines: [
|
|
352
|
-
"Code Graph is
|
|
353
|
-
"
|
|
602
|
+
"Code Graph is available on Free within capacity caps; first answers improve as symbols index.",
|
|
603
|
+
"For the first PR, run snipara_code_impact or companion code impact before editing."
|
|
354
604
|
]
|
|
355
605
|
}
|
|
356
606
|
]
|
|
357
607
|
};
|
|
358
608
|
}
|
|
359
|
-
function createActivationManifest(answers, detection, mode = "default") {
|
|
609
|
+
function createActivationManifest(answers, detection, mode = "default", scan) {
|
|
360
610
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
361
611
|
const hasHostedConfig = answers.profile !== "runtime-only";
|
|
362
612
|
const hasCompanion = answers.installCompanion;
|
|
363
|
-
const localScan = detectLocalActivationScan(detection);
|
|
613
|
+
const localScan = scan ?? detectLocalActivationScan(detection);
|
|
614
|
+
const dayZeroQuery = "What is this project, what is currently in flight, and what should an agent know before making the first edit?";
|
|
364
615
|
return {
|
|
365
616
|
version: "create-snipara-activation-v1",
|
|
366
617
|
activationId: buildActivationId(),
|
|
367
618
|
mode,
|
|
619
|
+
activationMetric: {
|
|
620
|
+
name: "time_to_how_did_it_know_that",
|
|
621
|
+
targetMinutes: 5,
|
|
622
|
+
proof: "A correct, non-obvious statement about this repository from the first answer pack."
|
|
623
|
+
},
|
|
624
|
+
dayZeroQuery: {
|
|
625
|
+
tool: "snipara_context_query",
|
|
626
|
+
query: dayZeroQuery,
|
|
627
|
+
expectedOutcome: "First own-repo answer pack before the user reads product docs."
|
|
628
|
+
},
|
|
629
|
+
dayZeroCorpus: createDayZeroCorpusReport(
|
|
630
|
+
"queued",
|
|
631
|
+
collectDayZeroCorpusDocuments(localScan),
|
|
632
|
+
"README and docs Markdown will be submitted to hosted context before the first day-zero query when live hosted checks are enabled."
|
|
633
|
+
),
|
|
634
|
+
agentReadiness: buildAgentReadinessReport(localScan),
|
|
635
|
+
contextEconomy: buildContextEconomyReport(localScan, dayZeroQuery),
|
|
368
636
|
projectSlug: answers.projectSlug,
|
|
369
637
|
projectId: answers.projectId || null,
|
|
370
638
|
repoPath: process.cwd(),
|
|
@@ -390,12 +658,12 @@ function createActivationManifest(answers, detection, mode = "default") {
|
|
|
390
658
|
hasCompanion ? "Local source activation will create a repository snapshot." : "Install companion later to activate local source context."
|
|
391
659
|
),
|
|
392
660
|
contextIndex: createActivationLane(
|
|
393
|
-
hasCompanion ? "queued" : "skipped",
|
|
394
|
-
hasCompanion ? "Context indexing starts from local docs and instruction files." : "Context indexing requires companion or hosted provider sync."
|
|
661
|
+
hasHostedConfig || hasCompanion ? "queued" : "skipped",
|
|
662
|
+
hasCompanion ? "Context indexing starts from local docs and instruction files." : hasHostedConfig ? "Day-zero README/docs sync will seed hosted context when live hosted checks run." : "Context indexing requires companion or hosted provider sync."
|
|
395
663
|
),
|
|
396
664
|
codeGraph: createActivationLane(
|
|
397
665
|
hasCompanion ? "queued" : "skipped",
|
|
398
|
-
hasCompanion ? "Code Graph indexing starts from local source overlay." : "Code Graph
|
|
666
|
+
hasCompanion ? "Free Code Graph indexing starts from local source overlay within capacity caps." : "Free Code Graph is available after companion or hosted provider sync."
|
|
399
667
|
),
|
|
400
668
|
memoryCandidates: createActivationLane(
|
|
401
669
|
hasCompanion ? "needs_review" : "skipped",
|
|
@@ -407,7 +675,7 @@ function createActivationManifest(answers, detection, mode = "default") {
|
|
|
407
675
|
),
|
|
408
676
|
firstBrief: createActivationLane(
|
|
409
677
|
"ready",
|
|
410
|
-
"First Work Brief
|
|
678
|
+
"First Work Brief and day-zero answer-pack prompt generated from local setup signals."
|
|
411
679
|
),
|
|
412
680
|
githubSync: createActivationLane(
|
|
413
681
|
answers.configureGitHub && answers.githubRepo ? "queued" : "skipped",
|
|
@@ -495,6 +763,115 @@ function runLocalSourceActivation(answers, manifest) {
|
|
|
495
763
|
}
|
|
496
764
|
updateActivationManifest(manifest);
|
|
497
765
|
}
|
|
766
|
+
async function runHostedDayZeroCorpusSync(answers, manifest, enabled) {
|
|
767
|
+
if (answers.profile === "runtime-only") {
|
|
768
|
+
manifest.dayZeroCorpus = createDayZeroCorpusReport(
|
|
769
|
+
"skipped",
|
|
770
|
+
[],
|
|
771
|
+
"Runtime-only profile skipped Hosted MCP config and hosted document sync."
|
|
772
|
+
);
|
|
773
|
+
updateActivationManifest(manifest);
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (!enabled) {
|
|
777
|
+
const documents2 = collectDayZeroCorpusDocuments(manifest.localScan);
|
|
778
|
+
manifest.dayZeroCorpus = createDayZeroCorpusReport(
|
|
779
|
+
"skipped",
|
|
780
|
+
documents2,
|
|
781
|
+
"Skipped because hosted validation was disabled with --skip-test."
|
|
782
|
+
);
|
|
783
|
+
updateActivationManifest(manifest);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (answers.apiKeyType === "none" || answers.apiKey.includes("YOUR_")) {
|
|
787
|
+
manifest.dayZeroCorpus = createDayZeroCorpusReport(
|
|
788
|
+
"skipped",
|
|
789
|
+
[],
|
|
790
|
+
"Skipped because no live hosted API key was available."
|
|
791
|
+
);
|
|
792
|
+
updateActivationManifest(manifest);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const documents = collectDayZeroCorpusDocuments(manifest.localScan);
|
|
796
|
+
if (documents.length === 0) {
|
|
797
|
+
manifest.dayZeroCorpus = createDayZeroCorpusReport(
|
|
798
|
+
"skipped",
|
|
799
|
+
[],
|
|
800
|
+
"No README or docs Markdown files were found to seed hosted context."
|
|
801
|
+
);
|
|
802
|
+
if (manifest.lanes.contextIndex.status === "queued") {
|
|
803
|
+
manifest.lanes.contextIndex = createActivationLane(
|
|
804
|
+
"skipped",
|
|
805
|
+
"No README or docs Markdown files were found to seed hosted context."
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
updateActivationManifest(manifest);
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
const syncTool = "snipara_sync_documents";
|
|
812
|
+
const syncResponse = await callHostedMcp(answers, "tools/call", {
|
|
813
|
+
name: syncTool,
|
|
814
|
+
arguments: {
|
|
815
|
+
documents,
|
|
816
|
+
delete_missing: false
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
if (syncResponse.ok && !syncResponse.data.error) {
|
|
820
|
+
manifest.dayZeroCorpus = createDayZeroCorpusReport(
|
|
821
|
+
"submitted",
|
|
822
|
+
documents,
|
|
823
|
+
`Submitted ${documents.length} README/docs files to hosted context for the first day-zero answer pack.`,
|
|
824
|
+
{ tool: syncTool }
|
|
825
|
+
);
|
|
826
|
+
manifest.lanes.contextIndex = createActivationLane(
|
|
827
|
+
"indexing",
|
|
828
|
+
"Day-zero README/docs corpus submitted to hosted context indexing.",
|
|
829
|
+
syncTool
|
|
830
|
+
);
|
|
831
|
+
updateActivationManifest(manifest);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const uploadTool = "snipara_upload_document";
|
|
835
|
+
const uploadErrors = [];
|
|
836
|
+
for (const document of documents) {
|
|
837
|
+
const uploadResponse = await callHostedMcp(answers, "tools/call", {
|
|
838
|
+
name: uploadTool,
|
|
839
|
+
arguments: document
|
|
840
|
+
});
|
|
841
|
+
if (!uploadResponse.ok || uploadResponse.data.error) {
|
|
842
|
+
uploadErrors.push(
|
|
843
|
+
uploadResponse.ok ? describeJsonRpcError(uploadResponse.data) : describeHostedHttpFailure(uploadResponse.status, uploadResponse.bodyText)
|
|
844
|
+
);
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (uploadErrors.length === 0) {
|
|
849
|
+
manifest.dayZeroCorpus = createDayZeroCorpusReport(
|
|
850
|
+
"submitted",
|
|
851
|
+
documents,
|
|
852
|
+
`Submitted ${documents.length} README/docs files to hosted context for the first day-zero answer pack.`,
|
|
853
|
+
{ tool: uploadTool }
|
|
854
|
+
);
|
|
855
|
+
manifest.lanes.contextIndex = createActivationLane(
|
|
856
|
+
"indexing",
|
|
857
|
+
"Day-zero README/docs corpus submitted to hosted context indexing.",
|
|
858
|
+
uploadTool
|
|
859
|
+
);
|
|
860
|
+
} else {
|
|
861
|
+
const syncError = syncResponse.ok ? describeJsonRpcError(syncResponse.data) : describeHostedHttpFailure(syncResponse.status, syncResponse.bodyText);
|
|
862
|
+
const detail = `Could not submit the day-zero README/docs corpus. sync_documents: ${syncError}; upload_document: ${uploadErrors[0]}`;
|
|
863
|
+
manifest.dayZeroCorpus = createDayZeroCorpusReport("failed", documents, detail, {
|
|
864
|
+
tool: syncTool,
|
|
865
|
+
error: detail
|
|
866
|
+
});
|
|
867
|
+
manifest.lanes.contextIndex = failActivationLane(
|
|
868
|
+
"Hosted day-zero README/docs sync failed; the first answer may need GitHub, companion, or a later sync.",
|
|
869
|
+
syncTool,
|
|
870
|
+
new Error(detail)
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
updateActivationManifest(manifest);
|
|
874
|
+
}
|
|
498
875
|
function createActivationArtifacts(answers, manifest) {
|
|
499
876
|
const candidates = buildMemoryCandidatePreviews(manifest.localScan);
|
|
500
877
|
writeMemoryCandidates(candidates);
|
|
@@ -776,6 +1153,8 @@ function parseInitOptionsFromArgv(args) {
|
|
|
776
1153
|
async function runInit(options) {
|
|
777
1154
|
const normalizedOptions = normalizeInitOptions(options);
|
|
778
1155
|
const answers = await gatherConfiguration(normalizedOptions);
|
|
1156
|
+
const activationDetection = detectEnvironment();
|
|
1157
|
+
const activationScan = detectLocalActivationScan(activationDetection);
|
|
779
1158
|
if (answers.installMcp || answers.installRuntime || answers.installOrchestrator) {
|
|
780
1159
|
await installPythonPackages(answers, false);
|
|
781
1160
|
}
|
|
@@ -802,9 +1181,11 @@ async function runInit(options) {
|
|
|
802
1181
|
writeDoctorReport(validationReport);
|
|
803
1182
|
const activationManifest = createActivationManifest(
|
|
804
1183
|
answers,
|
|
805
|
-
|
|
806
|
-
normalizedOptions.starter ? "starter" : "default"
|
|
1184
|
+
activationDetection,
|
|
1185
|
+
normalizedOptions.starter ? "starter" : "default",
|
|
1186
|
+
activationScan
|
|
807
1187
|
);
|
|
1188
|
+
await runHostedDayZeroCorpusSync(answers, activationManifest, !normalizedOptions.skipTest);
|
|
808
1189
|
runLocalSourceActivation(answers, activationManifest);
|
|
809
1190
|
createActivationArtifacts(answers, activationManifest);
|
|
810
1191
|
printSuccess(answers, validationReport, activationManifest);
|
|
@@ -2201,8 +2582,8 @@ Agent task lifecycle:
|
|
|
2201
2582
|
work.
|
|
2202
2583
|
6. Use whichever structural code graph tools are exposed in the current session
|
|
2203
2584
|
for callers, imports, neighbors, and path questions. Tool exposure can vary.
|
|
2204
|
-
7.
|
|
2205
|
-
|
|
2585
|
+
7. Use \`snipara_code_symbol_card\` for agent-ready symbol context within plan
|
|
2586
|
+
capacity and \`snipara-companion code impact\` before risky multi-file changes, PR
|
|
2206
2587
|
reviews, routes, services, jobs, auth, billing, deployment, schema, migrations,
|
|
2207
2588
|
or explicit "what is missing" assessments.
|
|
2208
2589
|
8. Use \`snipara_plan\` for complex or multi-surface work in any plan when
|
|
@@ -2269,7 +2650,7 @@ ${EXPERIENCE_GOAL_SECTION}
|
|
|
2269
2650
|
- Use \`snipara_code_callers\`, \`snipara_code_imports\`, \`snipara_code_neighbors\`, or \`snipara_code_shortest_path\` for canonical structural code questions.
|
|
2270
2651
|
- For code impact and structural code questions, use \`snipara-companion code callers/imports/neighbors/shortest-path/impact\` first when \`snipara-companion\` is installed; it auto-selects hosted MCP for clean indexed code or the local overlay for dirty/ahead worktrees and reports \`sourceSelection\`. Use hosted \`snipara_code_impact\` only when companion is unavailable or after push/reindex for canonical graph impact.
|
|
2271
2652
|
- Run \`snipara-companion code impact\` before risky multi-file changes, PR reviews, routes, services, jobs, auth, billing, deployment, schema, migrations, or explicit "what is missing" assessments.
|
|
2272
|
-
-
|
|
2653
|
+
- Use \`snipara_code_symbol_card\` before editing important symbols within plan capacity.
|
|
2273
2654
|
- Check every \`snipara_code_*\` response for indexed commit SHA, indexing time, included files, coverage, and freshness warnings.
|
|
2274
2655
|
- Use local file reads, \`rg\`, \`git status --short\`, and tests for exact edits/current working tree; use Snipara for architecture and indexed code graph context.
|
|
2275
2656
|
- If a broad query is slow, retry once with a narrow keyword query before falling back to local search.
|
|
@@ -2309,7 +2690,7 @@ ${EXPERIENCE_GOAL_SECTION}
|
|
|
2309
2690
|
- Use \`snipara_code_callers\`, \`snipara_code_imports\`, \`snipara_code_neighbors\`, or \`snipara_code_shortest_path\` for canonical structural code questions.
|
|
2310
2691
|
- For code impact and structural code questions, use \`snipara-companion code callers/imports/neighbors/shortest-path/impact\` first when \`snipara-companion\` is installed; it auto-selects hosted MCP for clean indexed code or the local overlay for dirty/ahead worktrees and reports \`sourceSelection\`. Use hosted \`snipara_code_impact\` only when companion is unavailable or after push/reindex for canonical graph impact.
|
|
2311
2692
|
- Run \`snipara-companion code impact\` before risky multi-file changes, PR reviews, routes, services, jobs, auth, billing, deployment, schema, migrations, or explicit "what is missing" assessments.
|
|
2312
|
-
-
|
|
2693
|
+
- Use \`snipara_code_symbol_card\` before editing important symbols within plan capacity.
|
|
2313
2694
|
- Check every \`snipara_code_*\` response for indexed commit SHA, indexing time, included files, coverage, and freshness warnings.
|
|
2314
2695
|
- Use \`snipara_plan\` for complex or multi-surface work when available. Keep simple Q&A on targeted \`snipara_context_query\`; for FULL-mode audits, comparisons, roadmap/implementation planning, release readiness, or package-surface reviews, use \`snipara_decompose\` to preserve axes and \`snipara_multi_query\` for independent follow-up questions when Pro+ tools are exposed.
|
|
2315
2696
|
- Use local file reads, \`rg\`, \`git status --short\`, and tests for exact code edits and current working-tree state.
|
|
@@ -3121,7 +3502,7 @@ function getCompanionCommands(answers) {
|
|
|
3121
3502
|
{
|
|
3122
3503
|
title: "Analyze code impact before a risky change",
|
|
3123
3504
|
command: `${COMPANION_NPX_COMMAND} code impact --changed-files src/api/webhooks.ts tests/webhooks.test.ts --diff-summary "webhook retry hardening"`,
|
|
3124
|
-
purpose: "Use
|
|
3505
|
+
purpose: "Use code impact analysis within plan capacity to surface likely tests, docs, routes, config, and risks."
|
|
3125
3506
|
},
|
|
3126
3507
|
{
|
|
3127
3508
|
title: answers.installOrchestrator ? "Run an explicit production orchestration gate" : "Enable explicit production orchestration",
|
|
@@ -3292,7 +3673,7 @@ ${guidance.map((line) => `- ${line}`).join("\n")}
|
|
|
3292
3673
|
- Use \`answer_pack\` facts, caveats, source maps, and verification checks before making claims.
|
|
3293
3674
|
- For coding tasks, choose a workflow mode before editing: LITE for small single-phase changes, FULL managed workflow for multi-file, risky, release/deploy, architectural, compaction-prone, or future-maintainer-sensitive work.
|
|
3294
3675
|
- Use \`snipara_plan\` for complex or multi-surface work when exposed. Keep simple Q&A on targeted \`snipara_context_query\`; for FULL-mode audits, comparisons, roadmap/implementation planning, release readiness, or package-surface reviews, use \`snipara_decompose\` to preserve axes and \`snipara_multi_query\` for independent follow-up questions when Pro+ tools are exposed.
|
|
3295
|
-
- Use \`snipara_code_symbol_card\` for important symbols. For risky multi-file changes, PR reviews, routes, services, jobs, auth, billing, deployment, schema, migrations, or explicit "what is missing" assessments, use \`snipara-companion code impact\` first when companion is available; use hosted \`snipara_code_impact\` only when companion is unavailable or after push/reindex for canonical graph impact.
|
|
3676
|
+
- Use \`snipara_code_symbol_card\` for important symbols within plan capacity. For risky multi-file changes, PR reviews, routes, services, jobs, auth, billing, deployment, schema, migrations, or explicit "what is missing" assessments, use \`snipara-companion code impact\` first when companion is available; use hosted \`snipara_code_impact\` only when companion is unavailable or after push/reindex for canonical graph impact.
|
|
3296
3677
|
- Use the installed \`snipara-companion\` binary for low-latency hooks; use \`${COMPANION_NPX_COMMAND}\` for one-off terminal commands when freshness matters.
|
|
3297
3678
|
- If a broad query is slow, retry once with a narrow keyword query before falling back to local search.
|
|
3298
3679
|
- Use a JSON machine plan plus \`snipara-companion workflow start\`, \`phase-start\`, \`phase-commit\`, \`workflow resume\`, and \`final-commit\` for multi-phase work. After \`workflow resume\`, rerun \`workflow phase-start <phase_id>\` before editing again.
|
|
@@ -4117,6 +4498,33 @@ function printActivationSummary(manifest) {
|
|
|
4117
4498
|
console.log(chalk.dim(` - ${line}`));
|
|
4118
4499
|
}
|
|
4119
4500
|
}
|
|
4501
|
+
console.log(chalk.bold("\nDay-zero proof:\n"));
|
|
4502
|
+
console.log(` Tool: ${manifest.dayZeroQuery.tool}`);
|
|
4503
|
+
console.log(` Query: ${manifest.dayZeroQuery.query}`);
|
|
4504
|
+
console.log(
|
|
4505
|
+
chalk.dim(
|
|
4506
|
+
` Target: ${manifest.activationMetric.targetMinutes} minutes to a true, non-obvious own-repo answer.`
|
|
4507
|
+
)
|
|
4508
|
+
);
|
|
4509
|
+
console.log(
|
|
4510
|
+
chalk.dim(
|
|
4511
|
+
` Day-zero corpus: ${manifest.dayZeroCorpus.status} - ${manifest.dayZeroCorpus.detail}`
|
|
4512
|
+
)
|
|
4513
|
+
);
|
|
4514
|
+
console.log(chalk.bold("\nAgent readiness:\n"));
|
|
4515
|
+
console.log(` ${manifest.agentReadiness.summary}`);
|
|
4516
|
+
for (const finding of manifest.agentReadiness.stalenessFindings.slice(0, 3)) {
|
|
4517
|
+
console.log(chalk.dim(` - ${finding.source}: ${finding.summary}`));
|
|
4518
|
+
}
|
|
4519
|
+
console.log(chalk.bold("\nContext economy:\n"));
|
|
4520
|
+
console.log(
|
|
4521
|
+
` Estimated ${manifest.contextEconomy.estimatedRetrievedTokens.toLocaleString("en-US")} tokens retrieved instead of ${manifest.contextEconomy.rawWindowBaselineTokens.toLocaleString("en-US")} raw-window tokens.`
|
|
4522
|
+
);
|
|
4523
|
+
console.log(
|
|
4524
|
+
chalk.dim(
|
|
4525
|
+
` Saved about ${manifest.contextEconomy.estimatedSavedTokens.toLocaleString("en-US")} tokens (${manifest.contextEconomy.savingsPercent}%). ${manifest.contextEconomy.note}`
|
|
4526
|
+
)
|
|
4527
|
+
);
|
|
4120
4528
|
console.log(
|
|
4121
4529
|
chalk.dim(
|
|
4122
4530
|
`
|
package/package.json
CHANGED