@phuc1403/musketeer 0.1.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.
- package/INSTALLATION.md +22 -0
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/bin/musketeer.js +72 -0
- package/manifest.json +238 -0
- package/package.json +46 -0
- package/src/copier.js +119 -0
- package/src/lockfile.js +61 -0
- package/src/manifest-loader.js +46 -0
- package/src/muster.js +48 -0
- package/src/provisioner/detect.js +93 -0
- package/src/provisioner/guide.js +32 -0
- package/src/provisioner/index.js +113 -0
- package/src/provisioner/mcp.js +26 -0
- package/src/provisioner/os-package-manager.js +71 -0
- package/src/provisioner/venv.js +44 -0
- package/src/reconcile.js +94 -0
- package/src/resolver.js +130 -0
- package/src/schema.js +152 -0
- package/src/settings-merger.js +0 -0
- package/template/.claude/agents/code-reviewer.md +166 -0
- package/template/.claude/agents/hallmark-auditor.md +78 -0
- package/template/.claude/agents/hallmark-explorer.md +81 -0
- package/template/.claude/agents/researcher.md +33 -0
- package/template/.claude/hooks/block-migration-edits.cjs +31 -0
- package/template/.claude/hooks/format-json-hook.js +91 -0
- package/template/.claude/hooks/inject-design-docs.cjs +86 -0
- package/template/.claude/hooks/lib/colors.cjs +122 -0
- package/template/.claude/hooks/lib/git-info-cache.cjs +191 -0
- package/template/.claude/hooks/lib/transcript-parser.cjs +277 -0
- package/template/.claude/hooks/usage-context-awareness.cjs +166 -0
- package/template/.claude/hooks/validate-cml-hook.js +145 -0
- package/template/.claude/skills/adr-writer/SKILL.md +78 -0
- package/template/.claude/skills/adr-writer/references/adr-example.md +38 -0
- package/template/.claude/skills/adr-writer/references/adr-template.md +46 -0
- package/template/.claude/skills/architecture-characteristic-writer/SKILL.md +117 -0
- package/template/.claude/skills/architecture-characteristic-writer/assets/worksheet-template.md +40 -0
- package/template/.claude/skills/architecture-characteristic-writer/references/characteristics-catalog.md +106 -0
- package/template/.claude/skills/code-review/SKILL.md +54 -0
- package/template/.claude/skills/code-review/references/adversarial-review.md +223 -0
- package/template/.claude/skills/code-review/references/checklists/api.md +52 -0
- package/template/.claude/skills/code-review/references/checklists/base.md +100 -0
- package/template/.claude/skills/code-review/references/checklists/web-app.md +54 -0
- package/template/.claude/skills/context-map/SKILL.md +80 -0
- package/template/.claude/skills/context-map/example.cml +106 -0
- package/template/.claude/skills/context-map/reference/Aggregate/Aggregate.md +1 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/Bounded Context.md +41 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/businessModel.md +6 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/domainVisionStatement.md +3 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/evolution.md +6 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/implementationTechnology.md +1 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/implements.md +1 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/knowledgeLevel.md +5 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/realizes.md +9 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/refines.md +10 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/responsibilities.md +26 -0
- package/template/.claude/skills/context-map/reference/Bounded Context/type.md +23 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Anticorruption Layer.md +5 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Bounded Context Relationship.md +13 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Conformist.md +5 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Customer-Supplier (C-S).md +23 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Open Host Service.md +5 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Partnership (P).md +14 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Published Language.md +5 -0
- package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Shared Kernel (SK).md +13 -0
- package/template/.claude/skills/context-map/reference/Context Map.md +63 -0
- package/template/.claude/skills/context-map/reference/Domain/Domain.md +30 -0
- package/template/.claude/skills/context-map/reference/Domain/supports.md +34 -0
- package/template/.claude/skills/context-map/reference/Domain/type.md +3 -0
- package/template/.claude/skills/context-map/reference/Semantic Rules.md +33 -0
- package/template/.claude/skills/hallmark/SKILL.md +552 -0
- package/template/.claude/skills/hallmark/references/anti-patterns.md +412 -0
- package/template/.claude/skills/hallmark/references/assets.md +406 -0
- package/template/.claude/skills/hallmark/references/color.md +95 -0
- package/template/.claude/skills/hallmark/references/component-cookbook.md +256 -0
- package/template/.claude/skills/hallmark/references/components/c1-outlined-chip.md +12 -0
- package/template/.claude/skills/hallmark/references/components/c2-inline-form-as-cta.md +16 -0
- package/template/.claude/skills/hallmark/references/components/c3-typographic-link.md +8 -0
- package/template/.claude/skills/hallmark/references/components/c4-sticky-bottom-bar.md +16 -0
- package/template/.claude/skills/hallmark/references/components/f1-bento-grid.md +20 -0
- package/template/.claude/skills/hallmark/references/components/f2-sticky-scroll-stack.md +20 -0
- package/template/.claude/skills/hallmark/references/components/f3-tabular-spec-sheet.md +11 -0
- package/template/.claude/skills/hallmark/references/components/f4-step-sequence.md +11 -0
- package/template/.claude/skills/hallmark/references/components/f5-annotated-screenshot.md +11 -0
- package/template/.claude/skills/hallmark/references/components/f6-product-card-grid.md +41 -0
- package/template/.claude/skills/hallmark/references/components/ft1-mast-headed.md +13 -0
- package/template/.claude/skills/hallmark/references/components/ft2-inline-rule-single-line.md +10 -0
- package/template/.claude/skills/hallmark/references/components/ft3-index-style-category-list.md +12 -0
- package/template/.claude/skills/hallmark/references/components/ft4-dense-typographic.md +10 -0
- package/template/.claude/skills/hallmark/references/components/ft5-statement.md +21 -0
- package/template/.claude/skills/hallmark/references/components/ft6-letter-close.md +19 -0
- package/template/.claude/skills/hallmark/references/components/ft7-newsletter-first.md +27 -0
- package/template/.claude/skills/hallmark/references/components/ft8-marquee-scroll.md +25 -0
- package/template/.claude/skills/hallmark/references/components/h1-marquee.md +15 -0
- package/template/.claude/skills/hallmark/references/components/h2-split-diptych.md +15 -0
- package/template/.claude/skills/hallmark/references/components/h3-quote-led.md +11 -0
- package/template/.claude/skills/hallmark/references/components/h4-stat-led.md +14 -0
- package/template/.claude/skills/hallmark/references/components/h5-letter-hero.md +11 -0
- package/template/.claude/skills/hallmark/references/components/h6-photographic-fold.md +16 -0
- package/template/.claude/skills/hallmark/references/components/h7-demo-video-clipped-by-viewport-edge.md +27 -0
- package/template/.claude/skills/hallmark/references/components/h8-mockup-split-browser-framed.md +23 -0
- package/template/.claude/skills/hallmark/references/components/h9-custom-illustration-centerpiece.md +27 -0
- package/template/.claude/skills/hallmark/references/components/n1-wordmark-2-links.md +12 -0
- package/template/.claude/skills/hallmark/references/components/n10-floating-on-scroll-morph.md +19 -0
- package/template/.claude/skills/hallmark/references/components/n2-floating-chip.md +14 -0
- package/template/.claude/skills/hallmark/references/components/n3-side-rail.md +14 -0
- package/template/.claude/skills/hallmark/references/components/n4-hidden-behind-k.md +9 -0
- package/template/.claude/skills/hallmark/references/components/n5-floating-pill.md +28 -0
- package/template/.claude/skills/hallmark/references/components/n6-newspaper-masthead.md +24 -0
- package/template/.claude/skills/hallmark/references/components/n7-brutal-slab.md +22 -0
- package/template/.claude/skills/hallmark/references/components/n8-terminal-command.md +21 -0
- package/template/.claude/skills/hallmark/references/components/n9-edge-aligned-minimal.md +17 -0
- package/template/.claude/skills/hallmark/references/components/s1-left-margin-numbered.md +15 -0
- package/template/.claude/skills/hallmark/references/components/s2-hanging.md +13 -0
- package/template/.claude/skills/hallmark/references/components/s3-sticky-pinned.md +19 -0
- package/template/.claude/skills/hallmark/references/components/s4-inline-no-break.md +11 -0
- package/template/.claude/skills/hallmark/references/components/s5-bottom-anchored.md +13 -0
- package/template/.claude/skills/hallmark/references/components/t1-pull-quote-with-marginalia.md +12 -0
- package/template/.claude/skills/hallmark/references/components/t2-logo-wall-hairline.md +19 -0
- package/template/.claude/skills/hallmark/references/components/t3-single-huge-quote.md +11 -0
- package/template/.claude/skills/hallmark/references/components/t4-numbered-stat-strip.md +14 -0
- package/template/.claude/skills/hallmark/references/contract.md +24 -0
- package/template/.claude/skills/hallmark/references/copy.md +182 -0
- package/template/.claude/skills/hallmark/references/custom-craft.md +626 -0
- package/template/.claude/skills/hallmark/references/custom-theme.md +329 -0
- package/template/.claude/skills/hallmark/references/design-md.md +116 -0
- package/template/.claude/skills/hallmark/references/export-formats.md +328 -0
- package/template/.claude/skills/hallmark/references/floating-nav.md +89 -0
- package/template/.claude/skills/hallmark/references/genres/atmospheric.md +65 -0
- package/template/.claude/skills/hallmark/references/genres/editorial.md +70 -0
- package/template/.claude/skills/hallmark/references/genres/modern-minimal.md +67 -0
- package/template/.claude/skills/hallmark/references/genres/playful.md +65 -0
- package/template/.claude/skills/hallmark/references/hero-enrichment.md +474 -0
- package/template/.claude/skills/hallmark/references/imagery-kit.md +170 -0
- package/template/.claude/skills/hallmark/references/interaction-and-states.md +207 -0
- package/template/.claude/skills/hallmark/references/layout-and-space.md +111 -0
- package/template/.claude/skills/hallmark/references/macrostructures/01-bento-grid.md +35 -0
- package/template/.claude/skills/hallmark/references/macrostructures/02-long-document.md +34 -0
- package/template/.claude/skills/hallmark/references/macrostructures/03-marquee-hero.md +31 -0
- package/template/.claude/skills/hallmark/references/macrostructures/04-stat-led.md +32 -0
- package/template/.claude/skills/hallmark/references/macrostructures/05-workbench.md +32 -0
- package/template/.claude/skills/hallmark/references/macrostructures/06-conversational-faq.md +33 -0
- package/template/.claude/skills/hallmark/references/macrostructures/07-manifesto.md +32 -0
- package/template/.claude/skills/hallmark/references/macrostructures/08-photographic.md +34 -0
- package/template/.claude/skills/hallmark/references/macrostructures/09-quote-led.md +32 -0
- package/template/.claude/skills/hallmark/references/macrostructures/10-specimen.md +32 -0
- package/template/.claude/skills/hallmark/references/macrostructures/11-catalogue.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/12-letter.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/13-index-first.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/14-narrative-workflow.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/15-split-studio.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/16-feature-stack.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/17-type-specimen.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/18-portfolio-grid.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/19-map-diagram.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/20-ecosystem-index.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures/21-component-playground.md +23 -0
- package/template/.claude/skills/hallmark/references/macrostructures.md +89 -0
- package/template/.claude/skills/hallmark/references/microinteractions.md +260 -0
- package/template/.claude/skills/hallmark/references/motion.md +109 -0
- package/template/.claude/skills/hallmark/references/preview-examples.md +49 -0
- package/template/.claude/skills/hallmark/references/responsive.md +138 -0
- package/template/.claude/skills/hallmark/references/slop-test.md +205 -0
- package/template/.claude/skills/hallmark/references/structure.md +164 -0
- package/template/.claude/skills/hallmark/references/study.md +511 -0
- package/template/.claude/skills/hallmark/references/typography.md +243 -0
- package/template/.claude/skills/hallmark/references/verbs/audit.md +25 -0
- package/template/.claude/skills/hallmark/references/verbs/redesign.md +269 -0
- package/template/.claude/skills/hallmark-explore/SKILL.md +137 -0
- package/template/.claude/skills/hallmark-explore/references/direction-matrix.md +124 -0
- package/template/.claude/skills/hallmark-explore/references/explorer-call.md +68 -0
- package/template/.claude/skills/hallmark-explore/scripts/build_compare.py +174 -0
- package/template/.claude/skills/hallmark-explore/scripts/scaffold_exploration.py +122 -0
- package/template/.claude/skills/hallmark-loop/SKILL.md +105 -0
- package/template/.claude/skills/hallmark-loop/references/auditor-call.md +60 -0
- package/template/.claude/skills/hallmark-loop/references/capture.md +78 -0
- package/template/.claude/skills/hallmark-loop/references/loop-control.md +79 -0
- package/template/.claude/skills/handoff/SKILL.md +15 -0
- package/template/.claude/skills/install.ps1 +42 -0
- package/template/.claude/skills/install.sh +54 -0
- package/template/.claude/skills/knowledge-crunching/SKILL.md +130 -0
- package/template/.claude/skills/knowledge-crunching/assets/context.template.md +59 -0
- package/template/.claude/skills/knowledge-crunching/references/crunching-dialogue.md +113 -0
- package/template/.claude/skills/research/SKILL.md +69 -0
- package/template/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/template/.claude/skills/skill-creator/SKILL.md +149 -0
- package/template/.claude/skills/skill-creator/agents/analyzer.md +274 -0
- package/template/.claude/skills/skill-creator/agents/comparator.md +202 -0
- package/template/.claude/skills/skill-creator/agents/grader.md +223 -0
- package/template/.claude/skills/skill-creator/assets/eval_review.html +146 -0
- package/template/.claude/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/template/.claude/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/template/.claude/skills/skill-creator/references/benchmark-optimization-guide.md +86 -0
- package/template/.claude/skills/skill-creator/references/distribution-guide.md +79 -0
- package/template/.claude/skills/skill-creator/references/eval-infrastructure-guide.md +129 -0
- package/template/.claude/skills/skill-creator/references/eval-schemas.md +121 -0
- package/template/.claude/skills/skill-creator/references/mcp-skills-integration.md +71 -0
- package/template/.claude/skills/skill-creator/references/metadata-quality-criteria.md +94 -0
- package/template/.claude/skills/skill-creator/references/plugin-marketplace-hosting.md +104 -0
- package/template/.claude/skills/skill-creator/references/plugin-marketplace-overview.md +89 -0
- package/template/.claude/skills/skill-creator/references/plugin-marketplace-schema.md +93 -0
- package/template/.claude/skills/skill-creator/references/plugin-marketplace-sources.md +103 -0
- package/template/.claude/skills/skill-creator/references/plugin-marketplace-troubleshooting.md +76 -0
- package/template/.claude/skills/skill-creator/references/script-quality-criteria.md +106 -0
- package/template/.claude/skills/skill-creator/references/skill-anatomy-and-requirements.md +77 -0
- package/template/.claude/skills/skill-creator/references/skill-creation-workflow.md +151 -0
- package/template/.claude/skills/skill-creator/references/skill-design-patterns.md +75 -0
- package/template/.claude/skills/skill-creator/references/skillmark-benchmark-criteria.md +102 -0
- package/template/.claude/skills/skill-creator/references/structure-organization-criteria.md +114 -0
- package/template/.claude/skills/skill-creator/references/testing-and-iteration.md +78 -0
- package/template/.claude/skills/skill-creator/references/token-efficiency-criteria.md +74 -0
- package/template/.claude/skills/skill-creator/references/troubleshooting-guide.md +81 -0
- package/template/.claude/skills/skill-creator/references/validation-checklist.md +83 -0
- package/template/.claude/skills/skill-creator/references/writing-effective-instructions.md +88 -0
- package/template/.claude/skills/skill-creator/references/yaml-frontmatter-reference.md +92 -0
- package/template/.claude/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/template/.claude/skills/skill-creator/scripts/encoding_utils.py +36 -0
- package/template/.claude/skills/skill-creator/scripts/generate_report.py +326 -0
- package/template/.claude/skills/skill-creator/scripts/improve_description.py +248 -0
- package/template/.claude/skills/skill-creator/scripts/init_skill.py +360 -0
- package/template/.claude/skills/skill-creator/scripts/package_skill.py +143 -0
- package/template/.claude/skills/skill-creator/scripts/quick_validate.py +110 -0
- package/template/.claude/skills/skill-creator/scripts/run_eval.py +310 -0
- package/template/.claude/skills/skill-creator/scripts/run_loop.py +332 -0
- package/template/.claude/skills/skill-creator/scripts/utils.py +47 -0
- package/template/.claude/skills/tdd/SKILL.md +142 -0
- package/template/.claude/skills/tdd/assets/Directory.Build.props +26 -0
- package/template/.claude/skills/tdd/deep-modules.md +15 -0
- package/template/.claude/skills/tdd/dotnet-build-config.md +21 -0
- package/template/.claude/skills/tdd/interface-design.md +31 -0
- package/template/.claude/skills/tdd/mocking.md +59 -0
- package/template/.claude/skills/tdd/refactoring.md +10 -0
- package/template/.claude/skills/tdd/test-per-layer.md +69 -0
- package/template/.claude/skills/tdd/tests.md +61 -0
- package/template/.claude/statusline.cjs +0 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SessionStart hook: inject Sololingo's always-on design artifacts into every
|
|
3
|
+
// session so the design-first docs reliably "lead the code" (CLAUDE.md), instead
|
|
4
|
+
// of relying on me to remember to read them.
|
|
5
|
+
//
|
|
6
|
+
// Payload (~7K tokens, mostly the CONTEXT.md): the CML context map, the
|
|
7
|
+
// architecture-characteristics worksheet, the ADR index, and every bounded
|
|
8
|
+
// context's CONTEXT.md (globbed recursively, so new BCs are picked up with no
|
|
9
|
+
// config change). tasks.json / event-storm JSON under bounded-contexts are
|
|
10
|
+
// deliberately excluded — task-tracking and raw walls, not design context.
|
|
11
|
+
//
|
|
12
|
+
// Fails open: any error emits nothing and exits 0, so it can never block a session.
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
const path = require("path");
|
|
15
|
+
|
|
16
|
+
const root = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
17
|
+
|
|
18
|
+
// Recursively collect CONTEXT.md under a directory (BC ubiquitous-language models).
|
|
19
|
+
function findContextDocs(dir) {
|
|
20
|
+
const out = [];
|
|
21
|
+
let entries;
|
|
22
|
+
try {
|
|
23
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
24
|
+
} catch {
|
|
25
|
+
return out; // dir missing — skip
|
|
26
|
+
}
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
const full = path.join(dir, entry.name);
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
out.push(...findContextDocs(full));
|
|
31
|
+
} else if (entry.isFile() && entry.name === "CONTEXT.md") {
|
|
32
|
+
out.push(full);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const targets = [
|
|
40
|
+
path.join(root, "docs", "context-map.cml"),
|
|
41
|
+
path.join(root, "docs", "architecture-characteristics.md"),
|
|
42
|
+
path.join(root, "docs", "adr", "README.md"),
|
|
43
|
+
...findContextDocs(path.join(root, "docs", "bounded-contexts")),
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const sections = [];
|
|
47
|
+
for (const file of targets) {
|
|
48
|
+
let content;
|
|
49
|
+
try {
|
|
50
|
+
content = fs.readFileSync(file, "utf-8");
|
|
51
|
+
} catch {
|
|
52
|
+
continue; // missing/unreadable — skip, don't fail
|
|
53
|
+
}
|
|
54
|
+
const rel = path.relative(root, file).split(path.sep).join("/");
|
|
55
|
+
sections.push(`===== ${rel} =====\n${content.trimEnd()}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (sections.length === 0) {
|
|
59
|
+
process.exit(0); // nothing to inject
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const additionalContext =
|
|
63
|
+
"Sololingo design artifacts — injected every session." +
|
|
64
|
+
"Treat these as the source of truth for the domain model, context boundaries, architecture " +
|
|
65
|
+
"characteristics, and recorded decisions; consult them before any domain, " +
|
|
66
|
+
"architecture, or ADR work, and keep changes consistent with them.\n\n" +
|
|
67
|
+
"Each CONTEXT.md captures the decisions of the DOMAIN only — the ubiquitous " +
|
|
68
|
+
"language and model rules, kept vendor/decision-neutral. It bounds what the " +
|
|
69
|
+
"domain model sees, not what infrastructure may do: the ACL/infrastructure can " +
|
|
70
|
+
"legitimately key on more than the model names. (E.g. the domain only ever sees " +
|
|
71
|
+
"a learner's Email, never the auth method; the ACL may also match the OIDC `sub`.) " +
|
|
72
|
+
"That layering is intended, not a contradiction of CONTEXT.md.\n\n" +
|
|
73
|
+
sections.join("\n\n");
|
|
74
|
+
|
|
75
|
+
process.stdout.write(
|
|
76
|
+
JSON.stringify({
|
|
77
|
+
hookSpecificOutput: {
|
|
78
|
+
hookEventName: "SessionStart",
|
|
79
|
+
additionalContext,
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
process.exit(0);
|
|
84
|
+
} catch {
|
|
85
|
+
process.exit(0); // fail open
|
|
86
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ANSI Terminal Colors - Cross-platform color support for statusline
|
|
6
|
+
* Supports NO_COLOR, FORCE_COLOR, COLORTERM auto-detection
|
|
7
|
+
* @module colors
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ANSI escape codes (8-color basic palette)
|
|
11
|
+
const RESET = '\x1b[0m';
|
|
12
|
+
const DIM = '\x1b[2m';
|
|
13
|
+
const RED = '\x1b[31m';
|
|
14
|
+
const GREEN = '\x1b[32m';
|
|
15
|
+
const YELLOW = '\x1b[33m';
|
|
16
|
+
const MAGENTA = '\x1b[35m';
|
|
17
|
+
const CYAN = '\x1b[36m';
|
|
18
|
+
|
|
19
|
+
// Detect color support at module load (cached)
|
|
20
|
+
// Claude Code statusline runs via pipe but output displays in TTY - default to true
|
|
21
|
+
const shouldUseColor = (() => {
|
|
22
|
+
if (process.env.NO_COLOR) return false;
|
|
23
|
+
if (process.env.FORCE_COLOR) return true;
|
|
24
|
+
// Default true for statusline context (Claude Code handles TTY display)
|
|
25
|
+
return true;
|
|
26
|
+
})();
|
|
27
|
+
|
|
28
|
+
// Mutable override (set by statusline.cjs from config)
|
|
29
|
+
// null = use env detection, true/false = explicit override
|
|
30
|
+
let _colorOverride = null;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Set explicit color enable/disable override (from config)
|
|
34
|
+
* Pass null to revert to env-var detection
|
|
35
|
+
* @param {boolean} enabled
|
|
36
|
+
*/
|
|
37
|
+
function setColorEnabled(enabled) {
|
|
38
|
+
_colorOverride = enabled;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Determine if colors should be rendered, respecting env vars and config override
|
|
43
|
+
* NO_COLOR env var always takes precedence over config override
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
function isColorEnabled() {
|
|
47
|
+
// NO_COLOR env var is a hard override that always wins
|
|
48
|
+
if (process.env.NO_COLOR) return false;
|
|
49
|
+
if (_colorOverride !== null) return _colorOverride;
|
|
50
|
+
return shouldUseColor;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Detect 256-color support via COLORTERM
|
|
54
|
+
const has256Color = (() => {
|
|
55
|
+
const ct = process.env.COLORTERM;
|
|
56
|
+
return ct === 'truecolor' || ct === '24bit' || ct === '256color';
|
|
57
|
+
})();
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Wrap text with ANSI color code
|
|
61
|
+
* @param {string} text - Text to colorize
|
|
62
|
+
* @param {string} code - ANSI escape code
|
|
63
|
+
* @returns {string} Colorized text or plain text if colors disabled
|
|
64
|
+
*/
|
|
65
|
+
function colorize(text, code) {
|
|
66
|
+
if (!isColorEnabled()) return String(text);
|
|
67
|
+
return `${code}${text}${RESET}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function green(text) { return colorize(text, GREEN); }
|
|
71
|
+
function yellow(text) { return colorize(text, YELLOW); }
|
|
72
|
+
function red(text) { return colorize(text, RED); }
|
|
73
|
+
function cyan(text) { return colorize(text, CYAN); }
|
|
74
|
+
function magenta(text) { return colorize(text, MAGENTA); }
|
|
75
|
+
function dim(text) { return colorize(text, DIM); }
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get color code based on context percentage threshold
|
|
79
|
+
* @param {number} percent - Context usage percentage (0-100)
|
|
80
|
+
* @returns {string} ANSI color code
|
|
81
|
+
*/
|
|
82
|
+
function getContextColor(percent) {
|
|
83
|
+
if (percent >= 85) return RED;
|
|
84
|
+
if (percent >= 70) return YELLOW;
|
|
85
|
+
return GREEN;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Generate colored progress bar for context window
|
|
90
|
+
* Uses ▰▱ characters (smooth horizontal rectangles) for consistent rendering
|
|
91
|
+
* @param {number} percent - Usage percentage (0-100)
|
|
92
|
+
* @param {number} width - Bar width in characters (default 12)
|
|
93
|
+
* @returns {string} Unicode progress bar with threshold-based colors
|
|
94
|
+
*/
|
|
95
|
+
function coloredBar(percent, width = 12) {
|
|
96
|
+
const clamped = Math.max(0, Math.min(100, percent));
|
|
97
|
+
const filled = Math.round((clamped / 100) * width);
|
|
98
|
+
const empty = width - filled;
|
|
99
|
+
|
|
100
|
+
if (!isColorEnabled()) {
|
|
101
|
+
return '▰'.repeat(filled) + '▱'.repeat(empty);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const color = getContextColor(percent);
|
|
105
|
+
return `${color}${'▰'.repeat(filled)}${DIM}${'▱'.repeat(empty)}${RESET}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
RESET,
|
|
110
|
+
green,
|
|
111
|
+
yellow,
|
|
112
|
+
red,
|
|
113
|
+
cyan,
|
|
114
|
+
magenta,
|
|
115
|
+
dim,
|
|
116
|
+
getContextColor,
|
|
117
|
+
coloredBar,
|
|
118
|
+
shouldUseColor,
|
|
119
|
+
has256Color,
|
|
120
|
+
setColorEnabled,
|
|
121
|
+
isColorEnabled
|
|
122
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Git Info Cache - Cross-platform git information batching
|
|
6
|
+
*
|
|
7
|
+
* Problem: 5-6 git process spawns per statusline render are slow on Windows (CreateProcess overhead)
|
|
8
|
+
* Solution: Cache git query results for 3 seconds — subsequent renders read cache (zero processes)
|
|
9
|
+
*
|
|
10
|
+
* Performance: 5 spawns per render → event-driven refresh + 30s TTL fallback
|
|
11
|
+
* Cross-platform: No bash-only syntax (no 2>/dev/null), windowsHide on all exec calls
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { execSync } = require('child_process');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
|
|
19
|
+
// Cache TTL — long fallback for external changes (git checkout outside Claude)
|
|
20
|
+
// Active invalidation happens via PostToolUse hooks after Edit/Write/Bash
|
|
21
|
+
const CACHE_TTL = 30000;
|
|
22
|
+
const CACHE_MISS = Symbol('cache_miss');
|
|
23
|
+
const CACHE_SKIP = Symbol('cache_skip');
|
|
24
|
+
|
|
25
|
+
function isTimeoutError(error) {
|
|
26
|
+
if (!error) return false;
|
|
27
|
+
if (error.killed) return true;
|
|
28
|
+
if (error.signal === 'SIGTERM') return true;
|
|
29
|
+
return /timed out|etimedout/i.test(String(error.message || ''));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getExecTimeoutMs() {
|
|
33
|
+
const parsed = Number.parseInt(process.env.CK_GIT_TIMEOUT_MS || '', 10);
|
|
34
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
35
|
+
return 3000;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Safe command execution wrapper with optional cwd
|
|
40
|
+
* Timeout prevents hangs on slow/network-mounted repos
|
|
41
|
+
*/
|
|
42
|
+
function execIn(cmd, cwd) {
|
|
43
|
+
try {
|
|
44
|
+
return {
|
|
45
|
+
output: execSync(cmd, {
|
|
46
|
+
encoding: 'utf8',
|
|
47
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
48
|
+
windowsHide: true,
|
|
49
|
+
cwd: cwd || undefined,
|
|
50
|
+
timeout: getExecTimeoutMs()
|
|
51
|
+
}).trim(),
|
|
52
|
+
timedOut: false
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
output: '',
|
|
57
|
+
timedOut: isTimeoutError(error)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get cache file path for current working directory
|
|
64
|
+
*/
|
|
65
|
+
function getCachePath(cwd) {
|
|
66
|
+
const hash = require('crypto')
|
|
67
|
+
.createHash('md5')
|
|
68
|
+
.update(cwd)
|
|
69
|
+
.digest('hex')
|
|
70
|
+
.slice(0, 8);
|
|
71
|
+
return path.join(os.tmpdir(), `ck-git-cache-${hash}.json`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Read cache if valid (not expired). Returns CACHE_MISS on miss.
|
|
76
|
+
* No existsSync check (TOCTOU race) — just try read and catch.
|
|
77
|
+
*/
|
|
78
|
+
function readCache(cachePath, options = {}) {
|
|
79
|
+
const { allowStale = false } = options;
|
|
80
|
+
try {
|
|
81
|
+
const cache = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
82
|
+
if (Date.now() - cache.timestamp < CACHE_TTL || allowStale) {
|
|
83
|
+
return cache.data; // Can be null (non-git dir) or object (git info)
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// File missing, corrupted, or expired — all treated as cache miss
|
|
87
|
+
}
|
|
88
|
+
return CACHE_MISS;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Write cache atomically (temp file + rename to avoid partial reads on Windows)
|
|
93
|
+
*/
|
|
94
|
+
function writeCache(cachePath, data) {
|
|
95
|
+
const tmpPath = cachePath + '.tmp';
|
|
96
|
+
try {
|
|
97
|
+
fs.writeFileSync(tmpPath, JSON.stringify({ timestamp: Date.now(), data }));
|
|
98
|
+
fs.renameSync(tmpPath, cachePath);
|
|
99
|
+
} catch {
|
|
100
|
+
try { fs.unlinkSync(tmpPath); } catch {}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Count non-empty lines in a newline-delimited string
|
|
106
|
+
*/
|
|
107
|
+
function countLines(str) {
|
|
108
|
+
if (!str) return 0;
|
|
109
|
+
return str.split('\n').filter(l => l.trim()).length;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Fetch git info directly in-process
|
|
114
|
+
* The cache is what eliminates redundant spawns — not subprocess wrapping
|
|
115
|
+
* @param {string} cwd - Directory to run git commands in
|
|
116
|
+
* Returns: { branch, unstaged, staged, ahead, behind } or null if not git repo
|
|
117
|
+
*/
|
|
118
|
+
function fetchGitInfo(cwd) {
|
|
119
|
+
// Check if git repo (fast check) — run in target cwd, not process.cwd()
|
|
120
|
+
const repoCheck = execIn('git rev-parse --git-dir', cwd);
|
|
121
|
+
if (repoCheck.timedOut) return CACHE_SKIP;
|
|
122
|
+
if (!repoCheck.output) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const branchPrimary = execIn('git branch --show-current', cwd);
|
|
127
|
+
const branchFallback = execIn('git rev-parse --short HEAD', cwd);
|
|
128
|
+
const unstagedResult = execIn('git diff --name-only', cwd);
|
|
129
|
+
const stagedResult = execIn('git diff --cached --name-only', cwd);
|
|
130
|
+
const aheadBehindResult = execIn('git rev-list --left-right --count @{u}...HEAD', cwd);
|
|
131
|
+
|
|
132
|
+
if (
|
|
133
|
+
branchPrimary.timedOut ||
|
|
134
|
+
branchFallback.timedOut ||
|
|
135
|
+
unstagedResult.timedOut ||
|
|
136
|
+
stagedResult.timedOut ||
|
|
137
|
+
aheadBehindResult.timedOut
|
|
138
|
+
) {
|
|
139
|
+
return CACHE_SKIP;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const branch = branchPrimary.output || branchFallback.output;
|
|
143
|
+
const unstaged = countLines(unstagedResult.output);
|
|
144
|
+
const staged = countLines(stagedResult.output);
|
|
145
|
+
|
|
146
|
+
// Ahead/behind — no 2>/dev/null (invalid on Windows cmd.exe)
|
|
147
|
+
let ahead = 0;
|
|
148
|
+
let behind = 0;
|
|
149
|
+
if (aheadBehindResult.output) {
|
|
150
|
+
const parts = aheadBehindResult.output.split(/\s+/);
|
|
151
|
+
behind = parseInt(parts[0], 10) || 0;
|
|
152
|
+
ahead = parseInt(parts[1], 10) || 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { branch, unstaged, staged, ahead, behind };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get git info with caching
|
|
160
|
+
* Main export function used by statusline
|
|
161
|
+
*/
|
|
162
|
+
function getGitInfo(cwd = process.cwd()) {
|
|
163
|
+
const cachePath = getCachePath(cwd);
|
|
164
|
+
|
|
165
|
+
// Try cache first (includes cached null for non-git dirs)
|
|
166
|
+
const cached = readCache(cachePath);
|
|
167
|
+
if (cached !== CACHE_MISS) return cached;
|
|
168
|
+
|
|
169
|
+
// Cache miss or expired, fetch fresh data in target cwd
|
|
170
|
+
const data = fetchGitInfo(cwd);
|
|
171
|
+
if (data === CACHE_SKIP) {
|
|
172
|
+
// Timeout/transient failures should not poison cache as non-git.
|
|
173
|
+
// Prefer last known value (even stale) if available.
|
|
174
|
+
const stale = readCache(cachePath, { allowStale: true });
|
|
175
|
+
return stale === CACHE_MISS ? null : stale;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Cache both positive and null results (avoids re-spawning git in non-git dirs)
|
|
179
|
+
writeCache(cachePath, data);
|
|
180
|
+
|
|
181
|
+
return data;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Invalidate cache for a directory (call after file changes to trigger fresh git query)
|
|
186
|
+
*/
|
|
187
|
+
function invalidateCache(cwd = process.cwd()) {
|
|
188
|
+
try { fs.unlinkSync(getCachePath(cwd)); } catch {}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = { getGitInfo, invalidateCache };
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Transcript Parser - Extract tool/agent/todo state from session JSONL
|
|
6
|
+
* @module transcript-parser
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const readline = require('readline');
|
|
11
|
+
|
|
12
|
+
function isNativeTaskTodo(todo) {
|
|
13
|
+
return Boolean(todo && todo._source === 'native_task');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeTodo(todo) {
|
|
17
|
+
if (!todo || typeof todo !== 'object') return null;
|
|
18
|
+
const normalized = {
|
|
19
|
+
content: todo.content ?? '',
|
|
20
|
+
status: todo.status ?? 'pending',
|
|
21
|
+
activeForm: todo.activeForm ?? null
|
|
22
|
+
};
|
|
23
|
+
if (todo.id != null) normalized.id = todo.id;
|
|
24
|
+
return normalized;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function extractTaskIdFromString(text) {
|
|
28
|
+
if (!text || typeof text !== 'string') return null;
|
|
29
|
+
const trimmed = text.trim();
|
|
30
|
+
if (!trimmed) return null;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(trimmed);
|
|
34
|
+
return extractTaskIdFromValue(parsed);
|
|
35
|
+
} catch {
|
|
36
|
+
// Not JSON, continue with regex extraction.
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const match = trimmed.match(/["']?task[_-]?id["']?\s*[:=]\s*["']([^"']+)["']/i);
|
|
40
|
+
if (match && match[1]) return match[1];
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function extractTaskIdFromValue(value) {
|
|
45
|
+
if (value == null) return null;
|
|
46
|
+
|
|
47
|
+
if (typeof value === 'string') {
|
|
48
|
+
return extractTaskIdFromString(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (typeof value !== 'object') return null;
|
|
52
|
+
|
|
53
|
+
if (typeof value.taskId === 'string' || typeof value.taskId === 'number') {
|
|
54
|
+
return String(value.taskId);
|
|
55
|
+
}
|
|
56
|
+
if (typeof value.task_id === 'string' || typeof value.task_id === 'number') {
|
|
57
|
+
return String(value.task_id);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (Array.isArray(value)) {
|
|
61
|
+
for (const item of value) {
|
|
62
|
+
const taskId = extractTaskIdFromValue(item);
|
|
63
|
+
if (taskId) return taskId;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const fieldValue of Object.values(value)) {
|
|
69
|
+
const taskId = extractTaskIdFromValue(fieldValue);
|
|
70
|
+
if (taskId) return taskId;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Parse transcript JSONL file
|
|
77
|
+
* @param {string} transcriptPath - Path to transcript file
|
|
78
|
+
* @returns {Promise<TranscriptData>}
|
|
79
|
+
*/
|
|
80
|
+
async function parseTranscript(transcriptPath) {
|
|
81
|
+
const result = {
|
|
82
|
+
tools: [],
|
|
83
|
+
agents: [],
|
|
84
|
+
todos: [],
|
|
85
|
+
sessionStart: null
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (!transcriptPath || !fs.existsSync(transcriptPath)) {
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const toolMap = new Map();
|
|
93
|
+
const agentMap = new Map();
|
|
94
|
+
let latestTodos = [];
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const fileStream = fs.createReadStream(transcriptPath);
|
|
98
|
+
const rl = readline.createInterface({
|
|
99
|
+
input: fileStream,
|
|
100
|
+
crlfDelay: Infinity
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
for await (const line of rl) {
|
|
104
|
+
if (!line.trim()) continue;
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const entry = JSON.parse(line);
|
|
108
|
+
processEntry(entry, toolMap, agentMap, latestTodos, result);
|
|
109
|
+
} catch {
|
|
110
|
+
// Skip malformed lines
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
// Return partial results on error
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
result.tools = Array.from(toolMap.values()).slice(-20);
|
|
118
|
+
result.agents = Array.from(agentMap.values()).slice(-10);
|
|
119
|
+
result.todos = latestTodos
|
|
120
|
+
.map(normalizeTodo)
|
|
121
|
+
.filter(Boolean);
|
|
122
|
+
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Process single JSONL entry
|
|
128
|
+
* @param {Object} entry - Parsed JSON line
|
|
129
|
+
* @param {Map} toolMap - Tool tracking map
|
|
130
|
+
* @param {Map} agentMap - Agent tracking map
|
|
131
|
+
* @param {Array} latestTodos - Latest todo array reference
|
|
132
|
+
* @param {Object} result - Result object
|
|
133
|
+
*/
|
|
134
|
+
function processEntry(entry, toolMap, agentMap, latestTodos, result) {
|
|
135
|
+
const timestamp = entry.timestamp ? new Date(entry.timestamp) : new Date();
|
|
136
|
+
|
|
137
|
+
// Track session start
|
|
138
|
+
if (!result.sessionStart && entry.timestamp) {
|
|
139
|
+
result.sessionStart = timestamp;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const content = entry.message?.content;
|
|
143
|
+
if (!content || !Array.isArray(content)) return;
|
|
144
|
+
|
|
145
|
+
for (const block of content) {
|
|
146
|
+
// Handle tool_use blocks
|
|
147
|
+
if (block.type === 'tool_use' && block.id && block.name) {
|
|
148
|
+
if (block.name === 'Task') {
|
|
149
|
+
// Agent spawn
|
|
150
|
+
agentMap.set(block.id, {
|
|
151
|
+
id: block.id,
|
|
152
|
+
type: block.input?.subagent_type ?? 'unknown',
|
|
153
|
+
model: block.input?.model ?? null,
|
|
154
|
+
description: block.input?.description ?? null,
|
|
155
|
+
status: 'running',
|
|
156
|
+
startTime: timestamp,
|
|
157
|
+
endTime: null
|
|
158
|
+
});
|
|
159
|
+
} else if (block.name === 'TodoWrite') {
|
|
160
|
+
// Legacy: Replace todo array (deprecated, kept for backwards compatibility)
|
|
161
|
+
if (block.input?.todos && Array.isArray(block.input.todos)) {
|
|
162
|
+
latestTodos.length = 0;
|
|
163
|
+
latestTodos.push(
|
|
164
|
+
...block.input.todos.map(todo => ({
|
|
165
|
+
...todo,
|
|
166
|
+
_source: 'legacy_todowrite'
|
|
167
|
+
}))
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
} else if (block.name === 'TaskCreate') {
|
|
171
|
+
// Native Task API: add new task.
|
|
172
|
+
// Track by tool_use id first; hydrate real task id from matching tool_result when present.
|
|
173
|
+
if (block.input?.subject) {
|
|
174
|
+
latestTodos.push({
|
|
175
|
+
id: block.id,
|
|
176
|
+
content: block.input.subject,
|
|
177
|
+
status: 'pending',
|
|
178
|
+
activeForm: block.input.activeForm || null,
|
|
179
|
+
_source: 'native_task',
|
|
180
|
+
_toolUseId: block.id
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
} else if (block.name === 'TaskUpdate') {
|
|
184
|
+
// Native Task API: Update existing task status
|
|
185
|
+
// Match by taskId against native-task ids first.
|
|
186
|
+
// Numeric fallback maps to native-task creation order only (not legacy TodoWrite items).
|
|
187
|
+
if (block.input?.taskId && block.input?.status) {
|
|
188
|
+
const taskId = String(block.input.taskId);
|
|
189
|
+
const nativeTodos = latestTodos.filter(isNativeTaskTodo);
|
|
190
|
+
let task = nativeTodos.find(t => String(t.id) === taskId);
|
|
191
|
+
if (!task && /^\d+$/.test(taskId)) {
|
|
192
|
+
const idx = Number(taskId) - 1;
|
|
193
|
+
if (idx >= 0 && idx < nativeTodos.length) task = nativeTodos[idx];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (task) {
|
|
197
|
+
task.status = block.input.status;
|
|
198
|
+
if (Object.prototype.hasOwnProperty.call(block.input, 'activeForm')) {
|
|
199
|
+
task.activeForm = block.input.activeForm || null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
// Regular tool
|
|
205
|
+
toolMap.set(block.id, {
|
|
206
|
+
id: block.id,
|
|
207
|
+
name: block.name,
|
|
208
|
+
target: extractTarget(block.name, block.input),
|
|
209
|
+
status: 'running',
|
|
210
|
+
startTime: timestamp,
|
|
211
|
+
endTime: null
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Handle tool_result blocks
|
|
217
|
+
if (block.type === 'tool_result' && block.tool_use_id) {
|
|
218
|
+
const tool = toolMap.get(block.tool_use_id);
|
|
219
|
+
if (tool) {
|
|
220
|
+
tool.status = block.is_error ? 'error' : 'completed';
|
|
221
|
+
tool.endTime = timestamp;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const agent = agentMap.get(block.tool_use_id);
|
|
225
|
+
if (agent) {
|
|
226
|
+
agent.status = 'completed';
|
|
227
|
+
agent.endTime = timestamp;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const createdTask = latestTodos.find(
|
|
231
|
+
todo => isNativeTaskTodo(todo) && todo._toolUseId === block.tool_use_id
|
|
232
|
+
);
|
|
233
|
+
if (createdTask) {
|
|
234
|
+
const hydratedId = extractTaskIdFromValue(block.content);
|
|
235
|
+
if (hydratedId) {
|
|
236
|
+
createdTask.id = hydratedId;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Extract target from tool input
|
|
245
|
+
* @param {string} toolName - Tool name
|
|
246
|
+
* @param {Object} input - Tool input object
|
|
247
|
+
* @returns {string|null} - Extracted target
|
|
248
|
+
*/
|
|
249
|
+
function extractTarget(toolName, input) {
|
|
250
|
+
if (!input) return null;
|
|
251
|
+
|
|
252
|
+
switch (toolName) {
|
|
253
|
+
case 'Read':
|
|
254
|
+
case 'Write':
|
|
255
|
+
case 'Edit':
|
|
256
|
+
return input.file_path ?? input.path ?? null;
|
|
257
|
+
|
|
258
|
+
case 'Glob':
|
|
259
|
+
case 'Grep':
|
|
260
|
+
return input.pattern ?? null;
|
|
261
|
+
|
|
262
|
+
case 'Bash':
|
|
263
|
+
const cmd = input.command;
|
|
264
|
+
if (!cmd) return null;
|
|
265
|
+
return cmd.length > 30 ? cmd.slice(0, 30) + '...' : cmd;
|
|
266
|
+
|
|
267
|
+
default:
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
module.exports = {
|
|
273
|
+
parseTranscript,
|
|
274
|
+
// Export for testing
|
|
275
|
+
processEntry,
|
|
276
|
+
extractTarget
|
|
277
|
+
};
|