create-snipara 2.0.15 → 2.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +46 -1
  2. package/dist/index.js +579 -5
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -29,7 +29,7 @@ Use it for:
29
29
  - free account signup with reviewed Context + Memory
30
30
  - local project wiring, `doctor`, `repair`, and `upgrade`
31
31
  - local source activation without GitHub, plus optional GitHub repository sync and PR Answer Packs approval when the current folder is a GitHub repo
32
- - day-zero proof with a ready-to-ask grounded question and a small hosted README/docs seed sync for new projects; advanced briefs, memory candidates, and diagnostics remain available in the generated activation artifacts
32
+ - day-zero proof with a ready-to-ask grounded question, a small hosted README/docs seed sync, and an evidence-linked Project Bootstrap for new projects
33
33
 
34
34
  ## Why It Exists
35
35
 
@@ -137,6 +137,47 @@ That distinction matters:
137
137
  - optionally installs Claude Code agent hooks and companion local code overlay Git hooks
138
138
  - supports `doctor`, `repair`, `upgrade`, and `print-config`
139
139
 
140
+ ## Project Bootstrap: Cold-Start Intelligence
141
+
142
+ During setup, `create-snipara` builds a reviewable picture of the repository
143
+ before the first agent task. It writes both a machine-readable artifact and a
144
+ human-readable report:
145
+
146
+ - `.snipara/activation/project-bootstrap.json`
147
+ - `.snipara/activation/project-bootstrap.md`
148
+
149
+ The report summarizes eight dynamic sections. Counts come from the current
150
+ repository; they are not fixed demo numbers:
151
+
152
+ - architecture patterns
153
+ - conventions
154
+ - decision candidates
155
+ - procedures
156
+ - skills and candidate workflows
157
+ - recurring failure candidates
158
+ - critical code symbols
159
+ - verification patterns
160
+
161
+ The local pass uses bounded evidence from Git history, `AGENTS.md`,
162
+ `CLAUDE.md`, `README`, `docs/`, CI configuration, package scripts, test
163
+ patterns, and release history. Every item links back to its evidence. Directly
164
+ detected facts can be marked `observed`; inferred decisions, workflows, and
165
+ failure patterns remain `needs_review` and are not promoted to durable project
166
+ memory automatically.
167
+
168
+ The artifact also includes a coverage ledger. PR and issue history are marked
169
+ `unavailable` until the user explicitly connects a source provider. Critical
170
+ symbols are marked `deferred` until Code Graph indexing is available. Snipara
171
+ does not invent candidates to make these sections look complete.
172
+
173
+ After setup, start here:
174
+
175
+ > Review what Snipara learned about your project.
176
+
177
+ The Project Bootstrap summary is also carried into the First Work Brief so the
178
+ agent begins with the repository's inherited experience and its remaining
179
+ evidence gaps in view.
180
+
140
181
  ## Product Boundary
141
182
 
142
183
  Keep the package roles simple:
@@ -461,6 +502,10 @@ After setup, the project gets a local pack at `.snipara/companion/`:
461
502
  - `commands.json`: starter commands for the current project
462
503
  - `doctor.json`: most recent validation report
463
504
 
505
+ Activation evidence is kept separately under `.snipara/activation/`, including
506
+ the Project Bootstrap JSON/Markdown pair, First Work Brief, handoff, and memory
507
+ candidate review artifacts.
508
+
464
509
  This pack is meant to be the local source of truth for machine-level setup. When
465
510
  `snipara-companion` is installed, it includes business-folder onboarding commands
466
511
  such as:
package/dist/index.js CHANGED
@@ -13,8 +13,8 @@ import { homedir } from "os";
13
13
  // package.json
14
14
  var package_default = {
15
15
  name: "create-snipara",
16
- version: "2.0.15",
17
- description: "Onboard Snipara Hosted MCP Context + Memory with auth, companion workflows, and optional Snipara Sandbox",
16
+ version: "2.0.16",
17
+ description: "Onboard Snipara Hosted MCP Context + Memory with project bootstrap intelligence and optional local workflows",
18
18
  main: "dist/index.js",
19
19
  type: "module",
20
20
  bin: {
@@ -107,6 +107,9 @@ var DEVICE_CLIENT_ID = "create-snipara";
107
107
  var DAY_ZERO_CORPUS_MAX_FILES = 8;
108
108
  var DAY_ZERO_CORPUS_MAX_FILE_CHARS = 12e3;
109
109
  var DAY_ZERO_CORPUS_MAX_TOTAL_CHARS = 5e4;
110
+ var PROJECT_BOOTSTRAP_SCHEMA_VERSION = "snipara.project_bootstrap.v1";
111
+ var PROJECT_BOOTSTRAP_MAX_WALKED_FILES = 2500;
112
+ var PROJECT_BOOTSTRAP_MAX_MATCHED_FILES = 80;
110
113
  var ACTIVATION_MANIFEST_SCHEMA_VERSION = "snipara.activation.manifest.v1";
111
114
  var ACTIVATION_CONSUMER_CONTRACT_VERSION = "snipara.editor.activation.v1";
112
115
  var EXPERIENCE_GOAL_SECTION = `## Experience Goal
@@ -154,6 +157,12 @@ function getFirstWorkBriefPath() {
154
157
  function getMemoryCandidatesPath() {
155
158
  return path.join(getActivationDir(), "memory-candidates.json");
156
159
  }
160
+ function getProjectBootstrapJsonPath() {
161
+ return path.join(getActivationDir(), "project-bootstrap.json");
162
+ }
163
+ function getProjectBootstrapMarkdownPath() {
164
+ return path.join(getActivationDir(), "project-bootstrap.md");
165
+ }
157
166
  function writeActivationManifest(manifest) {
158
167
  fs.mkdirSync(getActivationDir(), { recursive: true });
159
168
  const payload = JSON.stringify(manifest, null, 2) + "\n";
@@ -561,6 +570,23 @@ function listFilesUnder(relativeDir, maxFiles) {
561
570
  visit(root);
562
571
  return results;
563
572
  }
573
+ function isCreateSniparaGeneratedInstructionSource(relativePath) {
574
+ if (!["AGENTS.md", "CLAUDE.md", ".cursor/rules/snipara.mdc"].includes(relativePath)) {
575
+ return false;
576
+ }
577
+ try {
578
+ const prefix = fs.readFileSync(path.join(process.cwd(), relativePath), "utf8").slice(0, 4096);
579
+ return prefix.includes(
580
+ "## Snipara Context Workflow\n\nThis project uses Snipara Hosted MCP for project context and reviewed memory."
581
+ ) || prefix.includes(
582
+ "Canonical project context is in `AGENTS.md`. This file exists for Claude Code compatibility."
583
+ ) || prefix.includes(
584
+ "description: Use Snipara Hosted MCP for project context and reviewed memory."
585
+ );
586
+ } catch {
587
+ return false;
588
+ }
589
+ }
564
590
  function findDecisionArchaeologySources(cwd) {
565
591
  const exists = (relativePath) => fs.existsSync(path.join(cwd, relativePath));
566
592
  const directSources = [
@@ -578,7 +604,528 @@ function findDecisionArchaeologySources(cwd) {
578
604
  ...listFilesUnder("docs/decisions", 20),
579
605
  ...listFilesUnder(".cursor/rules", 20)
580
606
  ];
581
- return [.../* @__PURE__ */ new Set([...directSources, ...archaeologySources])].slice(0, 80);
607
+ return [.../* @__PURE__ */ new Set([...directSources, ...archaeologySources])].filter((source) => !isCreateSniparaGeneratedInstructionSource(source)).slice(0, 80);
608
+ }
609
+ function findBoundedProjectFiles(predicate, maxMatches = PROJECT_BOOTSTRAP_MAX_MATCHED_FILES) {
610
+ const ignoredDirectories = /* @__PURE__ */ new Set([
611
+ ".git",
612
+ ".next",
613
+ ".snipara",
614
+ ".turbo",
615
+ ".venv",
616
+ "build",
617
+ "coverage",
618
+ "dist",
619
+ "node_modules",
620
+ "target",
621
+ "vendor"
622
+ ]);
623
+ const results = [];
624
+ let walkedFiles = 0;
625
+ const visit = (directory) => {
626
+ if (walkedFiles >= PROJECT_BOOTSTRAP_MAX_WALKED_FILES || results.length >= maxMatches) {
627
+ return;
628
+ }
629
+ let entries;
630
+ try {
631
+ entries = fs.readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
632
+ } catch {
633
+ return;
634
+ }
635
+ for (const entry of entries) {
636
+ if (entry.isSymbolicLink()) {
637
+ continue;
638
+ }
639
+ const absolutePath = path.join(directory, entry.name);
640
+ if (entry.isDirectory()) {
641
+ if (!ignoredDirectories.has(entry.name)) {
642
+ visit(absolutePath);
643
+ }
644
+ } else if (entry.isFile()) {
645
+ walkedFiles += 1;
646
+ const relativePath = path.relative(process.cwd(), absolutePath).replace(/\\/g, "/");
647
+ if (predicate(relativePath)) {
648
+ results.push(relativePath);
649
+ }
650
+ }
651
+ if (walkedFiles >= PROJECT_BOOTSTRAP_MAX_WALKED_FILES || results.length >= maxMatches) {
652
+ return;
653
+ }
654
+ }
655
+ };
656
+ visit(process.cwd());
657
+ return results;
658
+ }
659
+ function readProjectPackageMetadata() {
660
+ const packageJsonPath = path.join(process.cwd(), "package.json");
661
+ if (!fs.existsSync(packageJsonPath)) {
662
+ return { scripts: {}, dependencies: [], workspaces: false };
663
+ }
664
+ try {
665
+ const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
666
+ const scripts = Object.fromEntries(
667
+ Object.entries(parsed.scripts ?? {}).filter(
668
+ (entry) => typeof entry[1] === "string"
669
+ )
670
+ );
671
+ const dependencies = [
672
+ ...Object.keys(parsed.dependencies ?? {}),
673
+ ...Object.keys(parsed.devDependencies ?? {}),
674
+ ...Object.keys(parsed.peerDependencies ?? {})
675
+ ];
676
+ return {
677
+ scripts,
678
+ dependencies: [...new Set(dependencies)].sort(),
679
+ workspaces: Boolean(parsed.workspaces)
680
+ };
681
+ } catch {
682
+ return { scripts: {}, dependencies: [], workspaces: false };
683
+ }
684
+ }
685
+ function readBootstrapGitSubjects(maxCount) {
686
+ try {
687
+ const output = execSync(`git log --pretty=format:%h%x20%s --max-count=${maxCount}`, {
688
+ cwd: process.cwd(),
689
+ encoding: "utf-8",
690
+ stdio: ["ignore", "pipe", "ignore"],
691
+ timeout: 1500
692
+ }).trim();
693
+ return output ? output.split(/\r?\n/).map((line) => line.trim()) : [];
694
+ } catch {
695
+ return [];
696
+ }
697
+ }
698
+ function readBootstrapReleaseTags() {
699
+ try {
700
+ const output = execSync("git tag --sort=-creatordate", {
701
+ cwd: process.cwd(),
702
+ encoding: "utf-8",
703
+ stdio: ["ignore", "pipe", "ignore"],
704
+ timeout: 1e3
705
+ }).trim();
706
+ return output ? output.split(/\r?\n/).slice(0, 20) : [];
707
+ } catch {
708
+ return [];
709
+ }
710
+ }
711
+ function createBootstrapItem(section, title, status, detail, evidence) {
712
+ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 56);
713
+ return {
714
+ id: `${section}:${slug || "candidate"}`,
715
+ title,
716
+ status,
717
+ detail,
718
+ evidence
719
+ };
720
+ }
721
+ function buildProjectBootstrap(scan) {
722
+ const cwd = process.cwd();
723
+ const exists = (relativePath) => fs.existsSync(path.join(cwd, relativePath));
724
+ const packageMetadata = readProjectPackageMetadata();
725
+ const allDetectedFiles = findBoundedProjectFiles(() => true, PROJECT_BOOTSTRAP_MAX_WALKED_FILES);
726
+ const testFiles = allDetectedFiles.filter(
727
+ (relativePath) => /(^|\/)(?:__tests__|tests?|spec)(\/|$)|(?:^|\/)[^/]+\.(?:test|spec)\.[^/]+$|(?:^|\/)test_[^/]+\.py$/i.test(
728
+ relativePath
729
+ )
730
+ );
731
+ const skillFiles = allDetectedFiles.filter(
732
+ (relativePath) => /(^|\/)SKILL\.md$/i.test(relativePath)
733
+ );
734
+ const ciFiles = [
735
+ ...allDetectedFiles.filter(
736
+ (relativePath) => /^\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath)
737
+ ),
738
+ ...[".gitlab-ci.yml", "azure-pipelines.yml", "Jenkinsfile", ".circleci/config.yml"].filter(
739
+ exists
740
+ )
741
+ ].filter((value, index, values) => values.indexOf(value) === index);
742
+ const instructionSources = scan.files.candidateDocs.filter(
743
+ (source) => classifyDecisionSource(source) === "agent_instruction"
744
+ );
745
+ const documentationSources = [
746
+ ...["README.md", "CHANGELOG.md", "MEMORY.md"].filter(exists),
747
+ ...listFilesUnder("docs", 40)
748
+ ].filter((value, index, values) => values.indexOf(value) === index);
749
+ const releaseTags = scan.git.detected ? readBootstrapReleaseTags() : [];
750
+ const gitSubjects = scan.git.detected ? readBootstrapGitSubjects(120) : [];
751
+ const architecture = [];
752
+ if (scan.languages.length > 0) {
753
+ architecture.push(
754
+ createBootstrapItem(
755
+ "architecture",
756
+ `Languages: ${scan.languages.join(", ")}`,
757
+ "observed",
758
+ "Detected from root manifests and source extensions.",
759
+ scan.files.hasPackageJson ? [{ source: "package.json", kind: "manifest" }] : [{ source: ".", kind: "filesystem_scan" }]
760
+ )
761
+ );
762
+ }
763
+ if (scan.packageManagers.length > 0) {
764
+ architecture.push(
765
+ createBootstrapItem(
766
+ "architecture",
767
+ `Package manager: ${scan.packageManagers.join(", ")}`,
768
+ "observed",
769
+ "Detected from lockfiles.",
770
+ scan.files.lockfiles.map((source) => ({ source, kind: "lockfile" }))
771
+ )
772
+ );
773
+ }
774
+ const workspaceEvidence = ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json"].filter(
775
+ exists
776
+ );
777
+ if (packageMetadata.workspaces || workspaceEvidence.length > 0) {
778
+ architecture.push(
779
+ createBootstrapItem(
780
+ "architecture",
781
+ "Workspace or monorepo layout",
782
+ "observed",
783
+ "A workspace declaration or workspace configuration file is present.",
784
+ (workspaceEvidence.length > 0 ? workspaceEvidence : ["package.json"]).map((source) => ({
785
+ source,
786
+ kind: "config"
787
+ }))
788
+ )
789
+ );
790
+ }
791
+ for (const directory of ["apps", "packages", "services", "libs"].filter(exists)) {
792
+ architecture.push(
793
+ createBootstrapItem(
794
+ "architecture",
795
+ `${directory}/ project boundary`,
796
+ "observed",
797
+ "Top-level project organization detected; no architectural meaning was inferred beyond its presence.",
798
+ [{ source: `${directory}/`, kind: "directory" }]
799
+ )
800
+ );
801
+ }
802
+ const frameworks = [
803
+ ["next", "Next.js"],
804
+ ["react", "React"],
805
+ ["@nestjs/core", "NestJS"],
806
+ ["express", "Express"],
807
+ ["fastify", "Fastify"],
808
+ ["vitest", "Vitest"],
809
+ ["jest", "Jest"]
810
+ ];
811
+ for (const [dependency, label] of frameworks) {
812
+ if (packageMetadata.dependencies.includes(dependency)) {
813
+ architecture.push(
814
+ createBootstrapItem(
815
+ "architecture",
816
+ `${label} dependency`,
817
+ "observed",
818
+ `The root manifest declares ${dependency}; its role still needs code-level confirmation.`,
819
+ [{ source: "package.json", kind: "manifest" }]
820
+ )
821
+ );
822
+ }
823
+ }
824
+ if (scan.files.hasDockerfile) {
825
+ architecture.push(
826
+ createBootstrapItem(
827
+ "architecture",
828
+ "Container build surface",
829
+ "observed",
830
+ "A root Dockerfile is present.",
831
+ [{ source: "Dockerfile", kind: "config" }]
832
+ )
833
+ );
834
+ }
835
+ const candidates = buildMemoryCandidatePreviews(scan);
836
+ const conventions = candidates.filter(
837
+ (candidate) => candidate.evidence === "agent_instruction" || candidate.evidence === "project_document"
838
+ ).slice(0, 16).map(
839
+ (candidate) => createBootstrapItem(
840
+ "conventions",
841
+ candidate.text.slice(0, 100),
842
+ "needs_review",
843
+ candidate.reason,
844
+ [{ source: candidate.source, kind: candidate.evidence }]
845
+ )
846
+ );
847
+ const decisions = candidates.filter(
848
+ (candidate) => ["git_commit_subject", "adr_or_decision_doc", "changelog"].includes(candidate.evidence)
849
+ ).slice(0, 12).map(
850
+ (candidate) => createBootstrapItem(
851
+ "decisions",
852
+ candidate.text.slice(0, 100),
853
+ "needs_review",
854
+ candidate.reason,
855
+ [{ source: candidate.source, kind: candidate.evidence }]
856
+ )
857
+ );
858
+ const procedureScriptPattern = /(^|:)(build|dev|start|lint|test|type-?check|check|verify|deploy|release|migrate|seed|smoke|e2e)(:|$)/i;
859
+ const procedures = Object.keys(packageMetadata.scripts).filter((name) => procedureScriptPattern.test(name)).slice(0, 20).map(
860
+ (name) => createBootstrapItem(
861
+ "procedures",
862
+ `package script: ${name}`,
863
+ "observed",
864
+ "A root package script is configured; its exact command remains in package.json.",
865
+ [{ source: "package.json", kind: "package_script" }]
866
+ )
867
+ );
868
+ for (const source of ciFiles.slice(0, 8)) {
869
+ procedures.push(
870
+ createBootstrapItem(
871
+ "procedures",
872
+ `CI workflow: ${path.basename(source)}`,
873
+ "observed",
874
+ "Automation configuration detected; individual jobs were not interpreted during the bounded scan.",
875
+ [{ source, kind: "ci_workflow" }]
876
+ )
877
+ );
878
+ }
879
+ const skills = skillFiles.slice(0, 12).map(
880
+ (source) => createBootstrapItem(
881
+ "skills",
882
+ `Existing skill: ${path.basename(path.dirname(source)) || "root"}`,
883
+ "observed",
884
+ "An explicit SKILL.md workflow was found.",
885
+ [{ source, kind: "skill_definition" }]
886
+ )
887
+ );
888
+ const verificationScriptNames = Object.keys(packageMetadata.scripts).filter(
889
+ (name) => /test|lint|type|check|verify|smoke|e2e|build/i.test(name)
890
+ );
891
+ if (verificationScriptNames.length >= 2 && skills.length < 12) {
892
+ skills.push(
893
+ createBootstrapItem(
894
+ "skills",
895
+ "Verification workflow candidate",
896
+ "needs_review",
897
+ `Related scripts detected: ${verificationScriptNames.slice(0, 8).join(", ")}. Review their intended order before promoting this to a skill.`,
898
+ [{ source: "package.json", kind: "package_scripts" }]
899
+ )
900
+ );
901
+ }
902
+ const failureTerms = [
903
+ ["rollback or revert", /\b(?:rollback|revert)\b/i],
904
+ ["hotfix", /\bhotfix\b/i],
905
+ ["regression", /\bregression\b/i],
906
+ ["flaky verification", /\b(?:flaky|flakiness)\b/i],
907
+ ["broken or failing behavior", /\b(?:broken|failing|failure)\b/i]
908
+ ];
909
+ const recurringFailures = failureTerms.flatMap(([label, matcher]) => {
910
+ const matches = gitSubjects.filter((subject) => matcher.test(subject)).slice(0, 6);
911
+ if (matches.length < 2) {
912
+ return [];
913
+ }
914
+ return [
915
+ createBootstrapItem(
916
+ "recurringFailures",
917
+ `${label} signal (${matches.length} commits)`,
918
+ "needs_review",
919
+ "Repeated commit-language is a signal, not proof of a common root cause. Review the linked commits before accepting this anti-pattern.",
920
+ matches.map((source) => ({ source, kind: "git_commit_subject" }))
921
+ )
922
+ ];
923
+ });
924
+ const verificationPatterns = verificationScriptNames.slice(0, 16).map(
925
+ (name) => createBootstrapItem(
926
+ "verificationPatterns",
927
+ `Verification script: ${name}`,
928
+ "observed",
929
+ "A root verification script is configured; its exact command remains in package.json.",
930
+ [{ source: "package.json", kind: "package_script" }]
931
+ )
932
+ );
933
+ const testPatternGroups = [
934
+ ["*.test.*", /\.test\.[^/]+$/i],
935
+ ["*.spec.*", /\.spec\.[^/]+$/i],
936
+ ["__tests__/", /(^|\/)__tests__\//i],
937
+ ["test_*.py", /(^|\/)test_[^/]+\.py$/i]
938
+ ];
939
+ for (const [label, matcher] of testPatternGroups) {
940
+ const matches = testFiles.filter((source) => matcher.test(source));
941
+ if (matches.length === 0) {
942
+ continue;
943
+ }
944
+ verificationPatterns.push(
945
+ createBootstrapItem(
946
+ "verificationPatterns",
947
+ `Test pattern: ${label}`,
948
+ "observed",
949
+ `${matches.length} matching file(s) found in the bounded scan.`,
950
+ matches.slice(0, 6).map((source) => ({ source, kind: "test_file" }))
951
+ )
952
+ );
953
+ }
954
+ const sections = {
955
+ architecture: {
956
+ title: "Architecture",
957
+ status: architecture.length > 0 ? "observed" : "deferred",
958
+ detail: "Manifest, filesystem, dependency, and configuration patterns only.",
959
+ items: architecture.slice(0, 20)
960
+ },
961
+ conventions: {
962
+ title: "Conventions",
963
+ status: conventions.length > 0 ? "needs_review" : "deferred",
964
+ detail: "Guidance-language candidates require human review before durable import.",
965
+ items: conventions
966
+ },
967
+ decisions: {
968
+ title: "Decisions",
969
+ status: decisions.length > 0 ? "needs_review" : "deferred",
970
+ detail: "Commit and decision-document candidates are never auto-approved.",
971
+ items: decisions
972
+ },
973
+ procedures: {
974
+ title: "Procedures",
975
+ status: procedures.length > 0 ? "observed" : "deferred",
976
+ detail: "Configured scripts and CI files; execution order is not inferred.",
977
+ items: procedures.slice(0, 24)
978
+ },
979
+ skills: {
980
+ title: "Skills",
981
+ status: skills.some((item) => item.status === "needs_review") ? "needs_review" : skills.length > 0 ? "observed" : "deferred",
982
+ detail: "Explicit skill definitions plus reviewable workflow candidates.",
983
+ items: skills
984
+ },
985
+ recurringFailures: {
986
+ title: "Recurring failures",
987
+ status: recurringFailures.length > 0 ? "needs_review" : "deferred",
988
+ detail: "Only repeated failure-language signals are shown; no root cause is inferred.",
989
+ items: recurringFailures
990
+ },
991
+ criticalCode: {
992
+ title: "Critical code",
993
+ status: "deferred",
994
+ detail: "Critical symbols require Code Graph indexing; the local bootstrap does not guess them.",
995
+ items: []
996
+ },
997
+ verificationPatterns: {
998
+ title: "Verification patterns",
999
+ status: verificationPatterns.length > 0 ? "observed" : "deferred",
1000
+ detail: "Configured checks and test naming patterns from the bounded local scan.",
1001
+ items: verificationPatterns.slice(0, 24)
1002
+ }
1003
+ };
1004
+ const summary = Object.fromEntries(
1005
+ Object.entries(sections).map(([key, section]) => [key, section.items.length])
1006
+ );
1007
+ const coverage = {
1008
+ gitHistory: createBootstrapCoverage(
1009
+ scan.git.detected ? "partial" : "unavailable",
1010
+ scan.git.detected ? "Inspected up to 120 recent commit subjects; commit bodies and diffs were not interpreted." : "No local Git repository was detected.",
1011
+ scan.git.detected ? ["git log"] : []
1012
+ ),
1013
+ pullRequests: createBootstrapCoverage(
1014
+ "unavailable",
1015
+ "Pull request history requires an explicitly connected provider.",
1016
+ []
1017
+ ),
1018
+ issues: createBootstrapCoverage(
1019
+ "unavailable",
1020
+ "Issue history requires an explicitly connected provider.",
1021
+ []
1022
+ ),
1023
+ instructions: createBootstrapCoverage(
1024
+ instructionSources.length > 0 ? "observed" : "unavailable",
1025
+ instructionSources.length > 0 ? "Project instruction files were included in the bounded local scan." : "No AGENTS.md, CLAUDE.md, or Copilot instruction file was found.",
1026
+ instructionSources
1027
+ ),
1028
+ documentation: createBootstrapCoverage(
1029
+ documentationSources.length > 0 ? "partial" : "unavailable",
1030
+ documentationSources.length > 0 ? `Inspected a bounded set of ${documentationSources.length} README, changelog, memory, and docs files.` : "No README, changelog, memory, or docs files were found.",
1031
+ documentationSources
1032
+ ),
1033
+ ci: createBootstrapCoverage(
1034
+ ciFiles.length > 0 ? "observed" : "unavailable",
1035
+ ciFiles.length > 0 ? "CI configuration files were detected." : "No standard CI configuration was found.",
1036
+ ciFiles
1037
+ ),
1038
+ packageScripts: createBootstrapCoverage(
1039
+ Object.keys(packageMetadata.scripts).length > 0 ? "observed" : "unavailable",
1040
+ Object.keys(packageMetadata.scripts).length > 0 ? "Root package scripts were inspected." : "No root package scripts were available.",
1041
+ Object.keys(packageMetadata.scripts).length > 0 ? ["package.json"] : []
1042
+ ),
1043
+ testPatterns: createBootstrapCoverage(
1044
+ testFiles.length > 0 ? "partial" : "unavailable",
1045
+ testFiles.length > 0 ? `Found ${testFiles.length} test-like files within a ${PROJECT_BOOTSTRAP_MAX_WALKED_FILES}-file scan cap.` : `No test-like files were found within the ${PROJECT_BOOTSTRAP_MAX_WALKED_FILES}-file scan cap.`,
1046
+ testFiles.slice(0, 20)
1047
+ ),
1048
+ releaseHistory: createBootstrapCoverage(
1049
+ releaseTags.length > 0 || exists("CHANGELOG.md") ? "partial" : "unavailable",
1050
+ releaseTags.length > 0 || exists("CHANGELOG.md") ? `Detected ${releaseTags.length} recent tag(s)${exists("CHANGELOG.md") ? " and CHANGELOG.md" : ""}.` : "No Git tags or root CHANGELOG.md were detected.",
1051
+ [
1052
+ ...releaseTags.map((tag) => `git tag ${tag}`),
1053
+ ...exists("CHANGELOG.md") ? ["CHANGELOG.md"] : []
1054
+ ]
1055
+ ),
1056
+ codeGraph: createBootstrapCoverage(
1057
+ "deferred",
1058
+ "Critical symbols and structural impact require Code Graph indexing after activation.",
1059
+ []
1060
+ )
1061
+ };
1062
+ return {
1063
+ schemaVersion: PROJECT_BOOTSTRAP_SCHEMA_VERSION,
1064
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1065
+ repoPath: cwd,
1066
+ reviewPrompt: "Review what Snipara learned about your project.",
1067
+ scanLimits: {
1068
+ walkedFiles: PROJECT_BOOTSTRAP_MAX_WALKED_FILES,
1069
+ matchedFilesPerQuery: PROJECT_BOOTSTRAP_MAX_MATCHED_FILES,
1070
+ gitCommitSubjects: 120
1071
+ },
1072
+ summary,
1073
+ coverage,
1074
+ sections
1075
+ };
1076
+ }
1077
+ function createBootstrapCoverage(status, detail, sources) {
1078
+ return { status, detail, sources };
1079
+ }
1080
+ function writeProjectBootstrap(report) {
1081
+ fs.mkdirSync(getActivationDir(), { recursive: true });
1082
+ fs.writeFileSync(getProjectBootstrapJsonPath(), JSON.stringify(report, null, 2) + "\n", "utf8");
1083
+ const lines = [
1084
+ "# PROJECT BOOTSTRAP",
1085
+ "",
1086
+ `Generated: ${report.generatedAt}`,
1087
+ `Repository: ${report.repoPath}`,
1088
+ "",
1089
+ "## Coverage",
1090
+ "",
1091
+ ...Object.entries(report.coverage).map(
1092
+ ([name, entry]) => `- ${name}: ${entry.status} \u2014 ${entry.detail}`
1093
+ ),
1094
+ "",
1095
+ ...Object.values(report.sections).flatMap((section) => [
1096
+ `## ${section.title} \u2014 ${section.items.length}`,
1097
+ "",
1098
+ section.detail,
1099
+ "",
1100
+ ...section.items.length > 0 ? section.items.flatMap((item) => [
1101
+ `- [${item.status}] ${item.title}`,
1102
+ ` ${item.detail}`,
1103
+ ...item.evidence.length > 0 ? [
1104
+ ` Evidence: ${item.evidence.map((entry) => entry.source).join(", ")}`
1105
+ ] : []
1106
+ ]) : ["- No evidence-backed item available yet."],
1107
+ ""
1108
+ ]),
1109
+ `**${report.reviewPrompt}**`,
1110
+ ""
1111
+ ];
1112
+ fs.writeFileSync(getProjectBootstrapMarkdownPath(), lines.join("\n"), "utf8");
1113
+ }
1114
+ function addProjectBootstrapToFirstWorkBrief(manifest, report) {
1115
+ manifest.firstBrief.sections.splice(1, 0, {
1116
+ title: "Project Bootstrap",
1117
+ lines: [
1118
+ `Architecture: ${report.summary.architecture} pattern(s).`,
1119
+ `Conventions: ${report.summary.conventions} candidate(s).`,
1120
+ `Decisions: ${report.summary.decisions} inferred candidate(s).`,
1121
+ `Procedures: ${report.summary.procedures} observed workflow(s).`,
1122
+ `Skills: ${report.summary.skills} existing or candidate workflow(s).`,
1123
+ `Recurring failures: ${report.summary.recurringFailures} review candidate(s).`,
1124
+ `Critical code: ${report.summary.criticalCode}; deferred until Code Graph indexes symbols.`,
1125
+ `Verification patterns: ${report.summary.verificationPatterns} observed pattern(s).`,
1126
+ report.reviewPrompt
1127
+ ]
1128
+ });
582
1129
  }
583
1130
  function createInitialFirstWorkBrief(answers, detection, scan) {
584
1131
  const surfaceContract = buildActivationSurfaceContract(answers.aiClient);
@@ -706,6 +1253,10 @@ function createActivationManifest(answers, detection, mode = "default", scan) {
706
1253
  hasCompanion ? "queued" : "skipped",
707
1254
  hasCompanion ? "Free Code Graph indexing starts from local source overlay within capacity caps." : "Free Code Graph is available after companion or hosted provider sync."
708
1255
  ),
1256
+ projectBootstrap: createActivationLane(
1257
+ "queued",
1258
+ "Bounded local cold-start analysis will prepare an evidence-linked Project Bootstrap."
1259
+ ),
709
1260
  memoryCandidates: createActivationLane(
710
1261
  hasCompanion ? "needs_review" : "skipped",
711
1262
  hasCompanion ? "Candidate decisions stay pending review; no durable memory is auto-approved." : "Install companion later to extract reviewable memory candidates."
@@ -728,7 +1279,9 @@ function createActivationManifest(answers, detection, mode = "default", scan) {
728
1279
  legacyManifestPath: path.relative(process.cwd(), getActivationManifestPath()),
729
1280
  firstBriefPath: null,
730
1281
  handoffPath: null,
731
- memoryCandidatesPath: null
1282
+ memoryCandidatesPath: null,
1283
+ projectBootstrapJsonPath: null,
1284
+ projectBootstrapMarkdownPath: null
732
1285
  },
733
1286
  nextActions: buildActivationNextActions(surfaceContract, answers),
734
1287
  firstBrief: createInitialFirstWorkBrief(answers, detection, localScan),
@@ -918,6 +1471,22 @@ async function runHostedDayZeroCorpusSync(answers, manifest, enabled) {
918
1471
  updateActivationManifest(manifest);
919
1472
  }
920
1473
  function createActivationArtifacts(answers, manifest) {
1474
+ const projectBootstrap = buildProjectBootstrap(manifest.localScan);
1475
+ writeProjectBootstrap(projectBootstrap);
1476
+ manifest.artifacts.projectBootstrapJsonPath = path.relative(
1477
+ process.cwd(),
1478
+ getProjectBootstrapJsonPath()
1479
+ );
1480
+ manifest.artifacts.projectBootstrapMarkdownPath = path.relative(
1481
+ process.cwd(),
1482
+ getProjectBootstrapMarkdownPath()
1483
+ );
1484
+ manifest.lanes.projectBootstrap = createActivationLane(
1485
+ "needs_review",
1486
+ "Evidence-linked Project Bootstrap generated; inferred candidates require review.",
1487
+ manifest.artifacts.projectBootstrapMarkdownPath
1488
+ );
1489
+ addProjectBootstrapToFirstWorkBrief(manifest, projectBootstrap);
921
1490
  const candidates = buildMemoryCandidatePreviews(manifest.localScan);
922
1491
  writeMemoryCandidates(candidates);
923
1492
  manifest.artifacts.memoryCandidatesPath = path.relative(process.cwd(), getMemoryCandidatesPath());
@@ -3530,7 +4099,9 @@ function buildEditorActivationConsumerContract(client) {
3530
4099
  activationManifest: ".snipara/activation/activation-manifest.json",
3531
4100
  firstBrief: ".snipara/activation/first-brief.md",
3532
4101
  handoff: ".snipara/activation/handoff.md",
3533
- memoryCandidates: ".snipara/activation/memory-candidates.json"
4102
+ memoryCandidates: ".snipara/activation/memory-candidates.json",
4103
+ projectBootstrapJson: ".snipara/activation/project-bootstrap.json",
4104
+ projectBootstrapMarkdown: ".snipara/activation/project-bootstrap.md"
3534
4105
  },
3535
4106
  requiredManifestFields: [
3536
4107
  "schemaVersion",
@@ -3539,7 +4110,10 @@ function buildEditorActivationConsumerContract(client) {
3539
4110
  "artifacts.activationManifestPath",
3540
4111
  "artifacts.firstBriefPath",
3541
4112
  "artifacts.handoffPath",
4113
+ "artifacts.projectBootstrapJsonPath",
4114
+ "artifacts.projectBootstrapMarkdownPath",
3542
4115
  "lanes.connection.status",
4116
+ "lanes.projectBootstrap.status",
3543
4117
  "lanes.firstBrief.status",
3544
4118
  "nextActions"
3545
4119
  ],
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-snipara",
3
- "version": "2.0.15",
4
- "description": "Onboard Snipara Hosted MCP Context + Memory with auth, companion workflows, and optional Snipara Sandbox",
3
+ "version": "2.0.16",
4
+ "description": "Onboard Snipara Hosted MCP Context + Memory with project bootstrap intelligence and optional local workflows",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "bin": {