sequant 1.11.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +93 -7
  2. package/dist/bin/cli.js +12 -9
  3. package/dist/src/commands/doctor.js +25 -20
  4. package/dist/src/commands/init.js +152 -65
  5. package/dist/src/commands/logs.js +7 -6
  6. package/dist/src/commands/run.d.ts +13 -1
  7. package/dist/src/commands/run.js +75 -12
  8. package/dist/src/commands/stats.js +67 -48
  9. package/dist/src/commands/status.js +30 -12
  10. package/dist/src/index.d.ts +6 -0
  11. package/dist/src/index.js +4 -0
  12. package/dist/src/lib/ac-linter.d.ts +116 -0
  13. package/dist/src/lib/ac-linter.js +304 -0
  14. package/dist/src/lib/cli-ui.d.ts +196 -0
  15. package/dist/src/lib/cli-ui.js +544 -0
  16. package/dist/src/lib/content-analyzer.d.ts +89 -0
  17. package/dist/src/lib/content-analyzer.js +437 -0
  18. package/dist/src/lib/phase-signal.d.ts +94 -0
  19. package/dist/src/lib/phase-signal.js +171 -0
  20. package/dist/src/lib/plugin-version-sync.d.ts +26 -0
  21. package/dist/src/lib/plugin-version-sync.js +91 -0
  22. package/dist/src/lib/project-name.d.ts +40 -0
  23. package/dist/src/lib/project-name.js +191 -0
  24. package/dist/src/lib/semgrep.d.ts +136 -0
  25. package/dist/src/lib/semgrep.js +406 -0
  26. package/dist/src/lib/solve-comment-parser.d.ts +84 -0
  27. package/dist/src/lib/solve-comment-parser.js +200 -0
  28. package/dist/src/lib/stack-config.d.ts +51 -0
  29. package/dist/src/lib/stack-config.js +77 -0
  30. package/dist/src/lib/stacks.d.ts +66 -0
  31. package/dist/src/lib/stacks.js +332 -0
  32. package/dist/src/lib/templates.d.ts +2 -0
  33. package/dist/src/lib/templates.js +12 -3
  34. package/dist/src/lib/upstream/assessment.d.ts +70 -0
  35. package/dist/src/lib/upstream/assessment.js +385 -0
  36. package/dist/src/lib/upstream/index.d.ts +11 -0
  37. package/dist/src/lib/upstream/index.js +14 -0
  38. package/dist/src/lib/upstream/issues.d.ts +38 -0
  39. package/dist/src/lib/upstream/issues.js +267 -0
  40. package/dist/src/lib/upstream/relevance.d.ts +50 -0
  41. package/dist/src/lib/upstream/relevance.js +209 -0
  42. package/dist/src/lib/upstream/report.d.ts +29 -0
  43. package/dist/src/lib/upstream/report.js +391 -0
  44. package/dist/src/lib/upstream/types.d.ts +207 -0
  45. package/dist/src/lib/upstream/types.js +5 -0
  46. package/dist/src/lib/workflow/log-writer.d.ts +1 -1
  47. package/dist/src/lib/workflow/metrics-schema.d.ts +3 -3
  48. package/dist/src/lib/workflow/qa-cache.d.ts +199 -0
  49. package/dist/src/lib/workflow/qa-cache.js +440 -0
  50. package/dist/src/lib/workflow/run-log-schema.d.ts +34 -6
  51. package/dist/src/lib/workflow/run-log-schema.js +12 -1
  52. package/dist/src/lib/workflow/state-schema.d.ts +4 -4
  53. package/dist/src/lib/workflow/types.d.ts +4 -0
  54. package/package.json +6 -1
  55. package/templates/hooks/pre-tool.sh +6 -0
  56. package/templates/memory/constitution.md +1 -5
  57. package/templates/skills/_shared/references/prompt-templates.md +350 -0
  58. package/templates/skills/_shared/references/subagent-types.md +131 -0
  59. package/templates/skills/exec/SKILL.md +82 -0
  60. package/templates/skills/fullsolve/SKILL.md +19 -2
  61. package/templates/skills/loop/SKILL.md +3 -1
  62. package/templates/skills/qa/SKILL.md +79 -9
  63. package/templates/skills/qa/references/quality-gates.md +85 -1
  64. package/templates/skills/qa/references/semgrep-rules.md +207 -0
  65. package/templates/skills/qa/scripts/quality-checks.sh +525 -15
  66. package/templates/skills/spec/SKILL.md +322 -9
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Stack configuration persistence
3
+ *
4
+ * Manages user's stack configuration in .sequant/stack.json
5
+ */
6
+ /**
7
+ * Stack entry with optional path for subdirectory stacks
8
+ */
9
+ export interface StackEntry {
10
+ name: string;
11
+ path?: string;
12
+ }
13
+ /**
14
+ * Persisted stack configuration
15
+ */
16
+ export interface StackConfigFile {
17
+ /** Primary stack (determines dev URL and main commands) */
18
+ primary: StackEntry;
19
+ /** Additional stacks to include in constitution notes */
20
+ additional?: StackEntry[];
21
+ /** When this configuration was last updated */
22
+ lastUpdated?: string;
23
+ }
24
+ /**
25
+ * Load stack configuration from .sequant/stack.json
26
+ *
27
+ * @returns Stack configuration or null if not found
28
+ */
29
+ export declare function loadStackConfig(): Promise<StackConfigFile | null>;
30
+ /**
31
+ * Save stack configuration to .sequant/stack.json
32
+ *
33
+ * @param config - Stack configuration to save
34
+ */
35
+ export declare function saveStackConfig(config: StackConfigFile): Promise<void>;
36
+ /**
37
+ * Check if stack configuration exists
38
+ */
39
+ export declare function hasStackConfig(): Promise<boolean>;
40
+ /**
41
+ * Get the primary stack name from configuration
42
+ *
43
+ * @returns Primary stack name or null if not configured
44
+ */
45
+ export declare function getPrimaryStack(): Promise<string | null>;
46
+ /**
47
+ * Get all configured stacks (primary + additional)
48
+ *
49
+ * @returns Array of all stack names
50
+ */
51
+ export declare function getAllConfiguredStacks(): Promise<string[]>;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Stack configuration persistence
3
+ *
4
+ * Manages user's stack configuration in .sequant/stack.json
5
+ */
6
+ import { fileExists, readFile, writeFile, ensureDir } from "./fs.js";
7
+ import { dirname } from "path";
8
+ const STACK_CONFIG_PATH = ".sequant/stack.json";
9
+ /**
10
+ * Load stack configuration from .sequant/stack.json
11
+ *
12
+ * @returns Stack configuration or null if not found
13
+ */
14
+ export async function loadStackConfig() {
15
+ try {
16
+ if (!(await fileExists(STACK_CONFIG_PATH))) {
17
+ return null;
18
+ }
19
+ const content = await readFile(STACK_CONFIG_PATH);
20
+ const config = JSON.parse(content);
21
+ // Validate required fields
22
+ if (!config.primary || !config.primary.name) {
23
+ return null;
24
+ }
25
+ return config;
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ /**
32
+ * Save stack configuration to .sequant/stack.json
33
+ *
34
+ * @param config - Stack configuration to save
35
+ */
36
+ export async function saveStackConfig(config) {
37
+ const configWithTimestamp = {
38
+ ...config,
39
+ lastUpdated: new Date().toISOString(),
40
+ };
41
+ await ensureDir(dirname(STACK_CONFIG_PATH));
42
+ await writeFile(STACK_CONFIG_PATH, JSON.stringify(configWithTimestamp, null, 2) + "\n");
43
+ }
44
+ /**
45
+ * Check if stack configuration exists
46
+ */
47
+ export async function hasStackConfig() {
48
+ return fileExists(STACK_CONFIG_PATH);
49
+ }
50
+ /**
51
+ * Get the primary stack name from configuration
52
+ *
53
+ * @returns Primary stack name or null if not configured
54
+ */
55
+ export async function getPrimaryStack() {
56
+ const config = await loadStackConfig();
57
+ return config?.primary?.name ?? null;
58
+ }
59
+ /**
60
+ * Get all configured stacks (primary + additional)
61
+ *
62
+ * @returns Array of all stack names
63
+ */
64
+ export async function getAllConfiguredStacks() {
65
+ const config = await loadStackConfig();
66
+ if (!config)
67
+ return [];
68
+ const stacks = [config.primary.name];
69
+ if (config.additional) {
70
+ for (const entry of config.additional) {
71
+ if (!stacks.includes(entry.name)) {
72
+ stacks.push(entry.name);
73
+ }
74
+ }
75
+ }
76
+ return stacks;
77
+ }
@@ -1,6 +1,30 @@
1
1
  /**
2
2
  * Stack detection and configuration
3
3
  */
4
+ /**
5
+ * Detected stack with location information
6
+ */
7
+ export interface DetectedStack {
8
+ /** Stack name (e.g., "nextjs", "python") */
9
+ stack: string;
10
+ /** Path relative to project root (empty string for root) */
11
+ path: string;
12
+ }
13
+ /**
14
+ * Stack configuration for persistence in .sequant/stack.json
15
+ */
16
+ export interface StackConfig_Persisted {
17
+ /** Primary stack for the project (determines dev URL, commands) */
18
+ primary: {
19
+ name: string;
20
+ path?: string;
21
+ };
22
+ /** Additional stacks to include in constitution notes */
23
+ additional?: Array<{
24
+ name: string;
25
+ path?: string;
26
+ }>;
27
+ }
4
28
  /**
5
29
  * Supported package managers
6
30
  */
@@ -46,4 +70,46 @@ export interface StackConfig {
46
70
  }
47
71
  export declare const STACKS: Record<string, StackConfig>;
48
72
  export declare function detectStack(): Promise<string | null>;
73
+ /**
74
+ * Detect stack in a specific directory
75
+ * Similar to detectStack but operates on a given path
76
+ *
77
+ * @param basePath - Directory path to check (relative to cwd)
78
+ * @returns Stack name or null if not detected
79
+ */
80
+ export declare function detectStackInDirectory(basePath: string): Promise<string | null>;
81
+ /**
82
+ * Detect all stacks in the repository
83
+ *
84
+ * Traverses root and immediate subdirectories (1 level deep) to find
85
+ * all stacks present in a monorepo or multi-stack project.
86
+ *
87
+ * @returns Array of detected stacks with their paths
88
+ */
89
+ export declare function detectAllStacks(): Promise<DetectedStack[]>;
49
90
  export declare function getStackConfig(stack: string): StackConfig;
91
+ /**
92
+ * Stack-specific notes for constitution templates
93
+ *
94
+ * These notes are injected into the constitution during setup to provide
95
+ * context-aware guidance for AI-assisted development.
96
+ */
97
+ export declare const STACK_NOTES: Record<string, string>;
98
+ /**
99
+ * Get stack-specific notes for constitution template
100
+ *
101
+ * @param stack - The stack name (e.g., "nextjs", "python", "rust")
102
+ * @returns The stack-specific notes markdown content
103
+ */
104
+ export declare function getStackNotes(stack: string): string;
105
+ /**
106
+ * Get combined stack notes for multiple stacks
107
+ *
108
+ * Combines notes from a primary stack and optional additional stacks
109
+ * into a single markdown section for multi-stack projects.
110
+ *
111
+ * @param primary - Primary stack name (determines primary tooling)
112
+ * @param additional - Additional stacks to include notes for
113
+ * @returns Combined stack notes markdown content
114
+ */
115
+ export declare function getMultiStackNotes(primary: string, additional?: string[]): string;
@@ -1,7 +1,25 @@
1
1
  /**
2
2
  * Stack detection and configuration
3
3
  */
4
+ import { readdir } from "fs/promises";
4
5
  import { fileExists, readFile } from "./fs.js";
6
+ /**
7
+ * Directories to skip during multi-stack detection
8
+ */
9
+ const SKIP_DIRECTORIES = [
10
+ "node_modules",
11
+ ".git",
12
+ "vendor",
13
+ "dist",
14
+ "build",
15
+ ".next",
16
+ ".nuxt",
17
+ ".output",
18
+ "__pycache__",
19
+ "target",
20
+ ".claude",
21
+ ".sequant",
22
+ ];
5
23
  /**
6
24
  * Package manager configurations
7
25
  */
@@ -307,6 +325,320 @@ export async function detectStack() {
307
325
  }
308
326
  return null;
309
327
  }
328
+ /**
329
+ * Detect stack in a specific directory
330
+ * Similar to detectStack but operates on a given path
331
+ *
332
+ * @param basePath - Directory path to check (relative to cwd)
333
+ * @returns Stack name or null if not detected
334
+ */
335
+ export async function detectStackInDirectory(basePath) {
336
+ const pathPrefix = basePath ? `${basePath}/` : "";
337
+ // Check for Next.js config files
338
+ for (const file of STACKS.nextjs.detection.files || []) {
339
+ if (await fileExists(`${pathPrefix}${file}`)) {
340
+ return "nextjs";
341
+ }
342
+ }
343
+ // Check for Astro config files
344
+ for (const file of STACKS.astro.detection.files || []) {
345
+ if (await fileExists(`${pathPrefix}${file}`)) {
346
+ return "astro";
347
+ }
348
+ }
349
+ // Check for SvelteKit config files
350
+ for (const file of STACKS.sveltekit.detection.files || []) {
351
+ if (await fileExists(`${pathPrefix}${file}`)) {
352
+ return "sveltekit";
353
+ }
354
+ }
355
+ // Check for Remix config files
356
+ for (const file of STACKS.remix.detection.files || []) {
357
+ if (await fileExists(`${pathPrefix}${file}`)) {
358
+ return "remix";
359
+ }
360
+ }
361
+ // Check for Nuxt config files
362
+ for (const file of STACKS.nuxt.detection.files || []) {
363
+ if (await fileExists(`${pathPrefix}${file}`)) {
364
+ return "nuxt";
365
+ }
366
+ }
367
+ // Check package.json for JS framework dependencies
368
+ const packageJsonPath = `${pathPrefix}package.json`;
369
+ if (await fileExists(packageJsonPath)) {
370
+ try {
371
+ const pkg = JSON.parse(await readFile(packageJsonPath));
372
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
373
+ if (deps.next)
374
+ return "nextjs";
375
+ if (deps.astro)
376
+ return "astro";
377
+ if (deps["@sveltejs/kit"])
378
+ return "sveltekit";
379
+ if (deps["@remix-run/react"])
380
+ return "remix";
381
+ if (deps.nuxt)
382
+ return "nuxt";
383
+ }
384
+ catch {
385
+ // Ignore parse errors
386
+ }
387
+ }
388
+ // Check for Rust
389
+ if (await fileExists(`${pathPrefix}Cargo.toml`)) {
390
+ return "rust";
391
+ }
392
+ // Check for Go
393
+ if (await fileExists(`${pathPrefix}go.mod`)) {
394
+ return "go";
395
+ }
396
+ // Check for Python
397
+ for (const file of STACKS.python.detection.files || []) {
398
+ if (await fileExists(`${pathPrefix}${file}`)) {
399
+ return "python";
400
+ }
401
+ }
402
+ return null;
403
+ }
404
+ /**
405
+ * Detect all stacks in the repository
406
+ *
407
+ * Traverses root and immediate subdirectories (1 level deep) to find
408
+ * all stacks present in a monorepo or multi-stack project.
409
+ *
410
+ * @returns Array of detected stacks with their paths
411
+ */
412
+ export async function detectAllStacks() {
413
+ const results = [];
414
+ // Check root directory
415
+ const rootStack = await detectStackInDirectory("");
416
+ if (rootStack) {
417
+ results.push({ stack: rootStack, path: "" });
418
+ }
419
+ // Check immediate subdirectories (1 level deep)
420
+ try {
421
+ const entries = await readdir(".", { withFileTypes: true });
422
+ for (const entry of entries) {
423
+ if (!entry.isDirectory())
424
+ continue;
425
+ if (SKIP_DIRECTORIES.includes(entry.name))
426
+ continue;
427
+ if (entry.name.startsWith("."))
428
+ continue; // Skip hidden directories
429
+ const subdirStack = await detectStackInDirectory(entry.name);
430
+ if (subdirStack) {
431
+ // Skip if same stack as root (likely a false positive)
432
+ // e.g., both root and /packages/web detect "nextjs"
433
+ // We want distinct stacks, not duplicates
434
+ const isDuplicate = results.some((r) => r.stack === subdirStack && r.path === "");
435
+ // Only skip if root has the same stack AND this is a common mono-repo pattern
436
+ // (like packages/, apps/, etc.)
437
+ if (!isDuplicate ||
438
+ entry.name === "frontend" ||
439
+ entry.name === "backend") {
440
+ results.push({ stack: subdirStack, path: entry.name });
441
+ }
442
+ }
443
+ }
444
+ }
445
+ catch {
446
+ // Directory read failed, return what we have
447
+ }
448
+ return results;
449
+ }
310
450
  export function getStackConfig(stack) {
311
451
  return STACKS[stack] || STACKS.generic;
312
452
  }
453
+ /**
454
+ * Stack-specific notes for constitution templates
455
+ *
456
+ * These notes are injected into the constitution during setup to provide
457
+ * context-aware guidance for AI-assisted development.
458
+ */
459
+ export const STACK_NOTES = {
460
+ nextjs: `### Next.js
461
+
462
+ **Testing:**
463
+ - Use Jest or Vitest with React Testing Library
464
+ - Place tests in \`__tests__/\` directories or as \`.test.ts(x)\` files
465
+ - Use \`next/jest\` preset for proper Next.js configuration
466
+ - Mock \`next/router\` and \`next/navigation\` in component tests
467
+
468
+ **Linting:**
469
+ - ESLint with \`eslint-config-next\` (included by default)
470
+ - Run \`npm run lint\` or \`next lint\`
471
+ - TypeScript strict mode recommended
472
+
473
+ **Build:**
474
+ - Output to \`.next/\` directory
475
+ - Use \`next build\` for production builds
476
+ - Static exports via \`output: 'export'\` in next.config.js`,
477
+ astro: `### Astro
478
+
479
+ **Testing:**
480
+ - Astro projects may not have test scripts configured by default
481
+ - Consider adding Vitest: \`npm install -D vitest\`
482
+ - Use \`@astrojs/test-utils\` for component testing
483
+
484
+ **Linting:**
485
+ - Consider ESLint with \`eslint-plugin-astro\`
486
+ - Install: \`npm install -D eslint eslint-plugin-astro\`
487
+
488
+ **Build:**
489
+ - Output to \`dist/\` by default
490
+ - Use \`astro build\` for production
491
+ - SSR mode available via adapters`,
492
+ sveltekit: `### SvelteKit
493
+
494
+ **Testing:**
495
+ - Use Vitest with \`@testing-library/svelte\`
496
+ - Playwright for E2E testing (often pre-configured)
497
+ - Place unit tests in \`src/\` alongside components
498
+
499
+ **Linting:**
500
+ - ESLint with \`eslint-plugin-svelte\`
501
+ - Prettier with \`prettier-plugin-svelte\`
502
+ - Run \`npm run lint\` and \`npm run check\`
503
+
504
+ **Build:**
505
+ - Output depends on adapter (node, static, vercel, etc.)
506
+ - Use \`svelte-kit build\` for production
507
+ - Type-check with \`svelte-kit sync && svelte-check\``,
508
+ remix: `### Remix
509
+
510
+ **Testing:**
511
+ - Use Vitest or Jest with React Testing Library
512
+ - Cypress or Playwright for E2E testing
513
+ - Test loaders and actions separately from components
514
+
515
+ **Linting:**
516
+ - ESLint with React and TypeScript plugins
517
+ - Remix doesn't include ESLint config by default
518
+
519
+ **Build:**
520
+ - Output to \`build/\` directory
521
+ - Use \`remix build\` for production
522
+ - Server and client bundles are separate`,
523
+ nuxt: `### Nuxt
524
+
525
+ **Testing:**
526
+ - Use Vitest with \`@nuxt/test-utils\`
527
+ - \`@vue/test-utils\` for component testing
528
+ - Playwright for E2E testing
529
+
530
+ **Linting:**
531
+ - ESLint with \`@nuxt/eslint-config\`
532
+ - Run \`npm run lint\` or \`nuxi lint\`
533
+
534
+ **Build:**
535
+ - Output to \`.output/\` directory
536
+ - Use \`nuxi build\` for production
537
+ - \`nuxi generate\` for static site generation`,
538
+ rust: `### Rust
539
+
540
+ **Testing:**
541
+ - Unit tests: \`#[cfg(test)]\` modules in source files
542
+ - Integration tests: \`tests/\` directory
543
+ - Run \`cargo test\` for all tests
544
+ - Use \`#[should_panic]\` for panic tests
545
+
546
+ **Linting:**
547
+ - \`cargo clippy\` for lints (install: \`rustup component add clippy\`)
548
+ - \`cargo fmt\` for formatting
549
+ - Consider \`cargo deny\` for dependency auditing
550
+
551
+ **Build:**
552
+ - Debug: \`cargo build\`
553
+ - Release: \`cargo build --release\`
554
+ - Output to \`target/debug/\` or \`target/release/\``,
555
+ python: `### Python
556
+
557
+ **Testing:**
558
+ - pytest is the standard test runner
559
+ - Place tests in \`tests/\` directory
560
+ - Use \`pytest-cov\` for coverage reports
561
+ - Fixtures in \`conftest.py\`
562
+
563
+ **Linting:**
564
+ - Ruff for fast linting and formatting: \`ruff check .\` and \`ruff format .\`
565
+ - Alternative: Black + isort + flake8
566
+ - mypy for type checking
567
+
568
+ **Build:**
569
+ - Use virtual environments (venv, poetry, pdm)
570
+ - \`python -m build\` for package builds
571
+ - \`pip install -e .\` for development installs`,
572
+ go: `### Go
573
+
574
+ **Testing:**
575
+ - Tests in \`*_test.go\` files alongside source
576
+ - Run \`go test ./...\` for all packages
577
+ - Use \`-v\` for verbose output, \`-race\` for race detection
578
+ - Table-driven tests are idiomatic
579
+
580
+ **Linting:**
581
+ - \`golangci-lint run\` for comprehensive linting
582
+ - \`go vet\` for basic checks
583
+ - \`gofmt\` or \`goimports\` for formatting
584
+
585
+ **Build:**
586
+ - \`go build ./...\` to compile
587
+ - \`go install\` to build and install
588
+ - Cross-compile with GOOS/GOARCH environment variables`,
589
+ generic: `### General Guidelines
590
+
591
+ **Testing:**
592
+ - Check for test scripts in package.json or equivalent
593
+ - Common patterns: \`npm test\`, \`pytest\`, \`go test\`, \`cargo test\`
594
+
595
+ **Linting:**
596
+ - Check for lint scripts or configuration files
597
+ - Common: ESLint, Prettier, Ruff, golangci-lint, clippy
598
+
599
+ **Build:**
600
+ - Check for build scripts and output directories
601
+ - Document build commands in README`,
602
+ };
603
+ /**
604
+ * Get stack-specific notes for constitution template
605
+ *
606
+ * @param stack - The stack name (e.g., "nextjs", "python", "rust")
607
+ * @returns The stack-specific notes markdown content
608
+ */
609
+ export function getStackNotes(stack) {
610
+ return STACK_NOTES[stack] || STACK_NOTES.generic;
611
+ }
612
+ /**
613
+ * Get combined stack notes for multiple stacks
614
+ *
615
+ * Combines notes from a primary stack and optional additional stacks
616
+ * into a single markdown section for multi-stack projects.
617
+ *
618
+ * @param primary - Primary stack name (determines primary tooling)
619
+ * @param additional - Additional stacks to include notes for
620
+ * @returns Combined stack notes markdown content
621
+ */
622
+ export function getMultiStackNotes(primary, additional = []) {
623
+ const sections = [];
624
+ // Add primary stack notes (with "Primary" marker)
625
+ const primaryNotes = STACK_NOTES[primary] || STACK_NOTES.generic;
626
+ if (additional.length > 0) {
627
+ // Replace the first line heading to include "(Primary)"
628
+ const modifiedPrimary = primaryNotes.replace(/^### (.+)$/m, "### $1 (Primary)");
629
+ sections.push(modifiedPrimary);
630
+ }
631
+ else {
632
+ sections.push(primaryNotes);
633
+ }
634
+ // Add additional stack notes
635
+ for (const stack of additional) {
636
+ if (stack === primary)
637
+ continue; // Skip if same as primary
638
+ const notes = STACK_NOTES[stack];
639
+ if (notes && notes !== STACK_NOTES.generic) {
640
+ sections.push(notes);
641
+ }
642
+ }
643
+ return sections.join("\n\n---\n\n");
644
+ }
@@ -32,6 +32,8 @@ export interface CopyTemplatesOptions {
32
32
  noSymlinks?: boolean;
33
33
  /** Force replacement of existing files/symlinks */
34
34
  force?: boolean;
35
+ /** Additional stacks to include in constitution notes (for multi-stack projects) */
36
+ additionalStacks?: string[];
35
37
  }
36
38
  /**
37
39
  * Create symlinks for files in a directory, with fallback to copy
@@ -2,11 +2,12 @@
2
2
  * Template management - copy and process templates
3
3
  */
4
4
  import { readdir, chmod } from "fs/promises";
5
- import { join, dirname, basename, relative, isAbsolute } from "path";
5
+ import { join, dirname, relative, isAbsolute } from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { readFile, writeFile, ensureDir, fileExists, isSymlink, createSymlink, removeFileOrSymlink, } from "./fs.js";
8
- import { getStackConfig } from "./stacks.js";
8
+ import { getStackConfig, getStackNotes, getMultiStackNotes } from "./stacks.js";
9
9
  import { isNativeWindows } from "./system.js";
10
+ import { getProjectName } from "./project-name.js";
10
11
  // Get the package templates directory
11
12
  function getTemplatesDir() {
12
13
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -157,11 +158,19 @@ export async function symlinkDir(srcDir, destDir, options = {}) {
157
158
  export async function copyTemplates(stack, tokens, options = {}) {
158
159
  const templatesDir = getTemplatesDir();
159
160
  const stackConfig = getStackConfig(stack);
161
+ // Detect project name from available sources (package.json, Cargo.toml, etc.)
162
+ const projectName = await getProjectName();
163
+ // Get stack-specific notes for constitution template
164
+ // Use multi-stack notes if additional stacks are provided
165
+ const stackNotes = options.additionalStacks && options.additionalStacks.length > 0
166
+ ? getMultiStackNotes(stack, options.additionalStacks)
167
+ : getStackNotes(stack);
160
168
  const variables = {
161
169
  ...stackConfig.variables,
162
170
  ...tokens,
163
- PROJECT_NAME: basename(process.cwd()) || "project",
171
+ PROJECT_NAME: projectName,
164
172
  STACK: stack,
173
+ STACK_NOTES: stackNotes,
165
174
  };
166
175
  async function copyDir(srcDir, destDir) {
167
176
  try {
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Main assessment module for upstream analysis
3
+ * Coordinates release fetching, analysis, and output generation
4
+ */
5
+ import type { AssessmentOptions, Baseline, BatchedAssessment, ReleaseData, UpstreamAssessment } from "./types.js";
6
+ /**
7
+ * Validate a version string to prevent command injection
8
+ * @throws Error if version format is invalid
9
+ */
10
+ export declare function validateVersion(version: string): void;
11
+ /**
12
+ * Check if gh CLI is available and authenticated
13
+ * @returns Object with availability status and error message if not available
14
+ */
15
+ export declare function checkGhCliAvailable(): Promise<{
16
+ available: boolean;
17
+ authenticated: boolean;
18
+ error?: string;
19
+ }>;
20
+ /**
21
+ * Fetch release data from Claude Code repository
22
+ */
23
+ export declare function fetchRelease(version?: string): Promise<ReleaseData | null>;
24
+ /**
25
+ * List releases from Claude Code repository
26
+ */
27
+ export declare function listReleases(limit?: number): Promise<Array<{
28
+ tagName: string;
29
+ publishedAt: string;
30
+ }>>;
31
+ /**
32
+ * Get releases since a specific version
33
+ */
34
+ export declare function getReleasesSince(sinceVersion: string): Promise<string[]>;
35
+ /**
36
+ * Load baseline from file
37
+ */
38
+ export declare function loadBaseline(path?: string): Promise<Baseline>;
39
+ /**
40
+ * Save baseline to file
41
+ */
42
+ export declare function saveBaseline(baseline: Baseline, path?: string): Promise<void>;
43
+ /**
44
+ * Update baseline with new assessed version
45
+ */
46
+ export declare function updateBaseline(version: string, path?: string): Promise<void>;
47
+ /**
48
+ * Check if a version has already been assessed
49
+ */
50
+ export declare function isAlreadyAssessed(version: string): Promise<boolean>;
51
+ /**
52
+ * Save local report
53
+ */
54
+ export declare function saveLocalReport(assessment: UpstreamAssessment): Promise<string>;
55
+ /**
56
+ * Run a single version assessment
57
+ */
58
+ export declare function assessVersion(version: string, options?: AssessmentOptions): Promise<UpstreamAssessment | null>;
59
+ /**
60
+ * Run assessment for latest release
61
+ */
62
+ export declare function assessLatest(options?: AssessmentOptions): Promise<UpstreamAssessment | null>;
63
+ /**
64
+ * Run batched assessment for multiple versions
65
+ */
66
+ export declare function assessSince(sinceVersion: string, options?: AssessmentOptions): Promise<BatchedAssessment | null>;
67
+ /**
68
+ * Main entry point for upstream assessment
69
+ */
70
+ export declare function runUpstream(options?: AssessmentOptions): Promise<UpstreamAssessment | BatchedAssessment | null>;