sillyspec 3.27.2 → 3.27.6
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/.claude/skills/sillyspec-execute/SKILL.md +8 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +2 -1
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-archive/SKILL.md +87 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-auto/SKILL.md +93 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-commit/SKILL.md +67 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-continue/SKILL.md +47 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-execute/SKILL.md +214 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-explore/SKILL.md +102 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-export/SKILL.md +57 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-init/SKILL.md +177 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-plan/SKILL.md +156 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-quick/SKILL.md +116 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-resume/SKILL.md +70 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-scan/SKILL.md +79 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-state/SKILL.md +64 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-status/SKILL.md +54 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-verify/SKILL.md +87 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
- package/.claude/worktrees/sillyspec-data-model-research/.gitattributes +2 -0
- package/.claude/worktrees/sillyspec-data-model-research/.husky/pre-push +20 -0
- package/.claude/worktrees/sillyspec-data-model-research/README.md +203 -0
- package/.claude/worktrees/sillyspec-data-model-research/SKILL.md +91 -0
- package/.claude/worktrees/sillyspec-data-model-research/bin/sillyspec.js +2 -0
- package/.claude/worktrees/sillyspec-data-model-research/logo.jpg +0 -0
- package/.claude/worktrees/sillyspec-data-model-research/package-lock.json +977 -0
- package/.claude/worktrees/sillyspec-data-model-research/package.json +39 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/favicon.jpg +0 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/index.html +16 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/logo.jpg +0 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-dashboard.html +836 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-overview.html +256 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/package.json +25 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/executor.js +95 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/index.js +679 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/parser.js +526 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/watcher.js +357 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/agent-session-log.js +803 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/change-list.js +290 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/change-risk-profile.js +270 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/check-primitives.js +65 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/classify-change.js +109 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/commit-suggest.js +202 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/config-cat.js +88 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/config-schema.js +328 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/constants.js +97 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/contract-matrix.js +480 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/datetime.js +20 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/db-engine.js +55 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/db.js +381 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/decision-distill.js +517 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/local-agent.js +155 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/sillyhub-mcp.js +245 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/probe.js +246 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/strategy.js +121 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/docs-check.js +825 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/docs-debt.js +393 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/docs-gate.js +107 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/doctor-diagnostics.js +1099 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/endpoint-extractor.js +420 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/foreign-declared.js +78 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/fs-atomic.js +73 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/git-helper.js +150 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/hooks/claude-pre-tool-use.cjs +130 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/hooks/worktree-guard.js +892 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/index.js +2798 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/init.js +626 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/knowledge-match.js +231 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/local-detect.js +128 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/local-register.js +104 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/machine-interface.js +543 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/migrate.js +127 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/module-impact.js +166 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/module-resolve.js +222 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/modules.js +580 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/change-registry.js +336 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/consistency-doctor.js +355 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/shared.js +36 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/stage-machine.js +594 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/step-store.js +265 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress.js +1304 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/quick-recommend.js +115 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/quicklog.js +852 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/review-tier.js +77 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/command.js +1595 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/complete-handlers.js +1552 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/complete.js +1259 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/concurrent-detect.js +156 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/gates.js +1084 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/multi-repo-context.js +208 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/next.js +128 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/prompt.js +843 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/quick-audit.js +186 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/scan-profile.js +269 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/shared.js +1562 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/stage.js +584 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run.js +23 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/scan-diff.js +388 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/scan-postcheck.js +512 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/scan-staleness.js +95 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/setup.js +414 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/client.js +597 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/config.js +62 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/spec-dir-typo.js +50 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/spec-sync.js +358 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-engine.js +171 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-spec.js +550 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract.js +889 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-review.js +597 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/archive.js +199 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm-auto.js +236 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm.js +617 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/cmd-existence.js +145 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/doctor.js +540 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/execute.js +1275 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/explore.js +43 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/index.js +29 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/knowledge.js +514 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan-postcheck.js +1344 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan.js +650 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/quick.js +167 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/scan.js +608 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/status.js +64 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/verify.js +246 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/sync.js +1470 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/task-review.js +1498 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/taskcard.js +210 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/verify-postcheck.js +1718 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/verify-probes.js +277 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/version.js +19 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/workflow.js +600 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/workspace.js +125 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/worktree-apply.js +1446 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/worktree-deps.js +576 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/worktree.js +1715 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/claude-instruction.md +23 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/taskcard-rules.md +28 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/testcase-design.md +12 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/verify-probes.md +32 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/archive-impact.yaml +79 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/scan-docs.yaml +132 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-archive/SKILL.md +87 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-auto/SKILL.md +93 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-commit/SKILL.md +67 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-continue/SKILL.md +47 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-execute/SKILL.md +214 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-explore/SKILL.md +102 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-export/SKILL.md +57 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-init/SKILL.md +177 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-plan/SKILL.md +156 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-propose/SKILL.md +87 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-quick/SKILL.md +116 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-resume/SKILL.md +70 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-scan/SKILL.md +79 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-state/SKILL.md +64 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-status/SKILL.md +54 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-verify/SKILL.md +87 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
- package/.worktrees/8d2d0392/.claude/skills/verify-per-user/SKILL.md +6 -0
- package/.worktrees/8d2d0392/.gitattributes +2 -0
- package/.worktrees/8d2d0392/.husky/pre-push +20 -0
- package/.worktrees/8d2d0392/README.md +203 -0
- package/.worktrees/8d2d0392/SKILL.md +91 -0
- package/.worktrees/8d2d0392/architecture-analysis.md +448 -0
- package/.worktrees/8d2d0392/bin/sillyspec.js +2 -0
- package/.worktrees/8d2d0392/eval/README.md +86 -0
- package/.worktrees/8d2d0392/eval/eval.config.json +28 -0
- package/.worktrees/8d2d0392/eval/lib/adapters/cli.mjs +63 -0
- package/.worktrees/8d2d0392/eval/lib/adapters/dryrun.mjs +27 -0
- package/.worktrees/8d2d0392/eval/lib/config.mjs +82 -0
- package/.worktrees/8d2d0392/eval/lib/db.mjs +76 -0
- package/.worktrees/8d2d0392/eval/lib/report.mjs +120 -0
- package/.worktrees/8d2d0392/eval/lib/runner.mjs +157 -0
- package/.worktrees/8d2d0392/eval/lib/tasks.mjs +73 -0
- package/.worktrees/8d2d0392/eval/lib/util.mjs +107 -0
- package/.worktrees/8d2d0392/eval/lib/workdir.mjs +30 -0
- package/.worktrees/8d2d0392/eval/run.mjs +83 -0
- package/.worktrees/8d2d0392/eval/tasks/001-slugify/files/package.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
- package/.worktrees/8d2d0392/eval/tasks/001-slugify/task.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/001-slugify/verify.mjs +28 -0
- package/.worktrees/8d2d0392/eval/tasks/002-each-day/files/package.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
- package/.worktrees/8d2d0392/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
- package/.worktrees/8d2d0392/eval/tasks/002-each-day/task.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/002-each-day/verify.mjs +31 -0
- package/.worktrees/8d2d0392/eval/tasks/003-word-report/files/data/notes.txt +5 -0
- package/.worktrees/8d2d0392/eval/tasks/003-word-report/files/package.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
- package/.worktrees/8d2d0392/eval/tasks/003-word-report/task.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/003-word-report/verify.mjs +38 -0
- package/.worktrees/8d2d0392/logo.jpg +0 -0
- package/.worktrees/8d2d0392/package-lock.json +977 -0
- package/.worktrees/8d2d0392/package.json +41 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/favicon.jpg +0 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/index.html +16 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/logo.jpg +0 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/prototype-dashboard.html +836 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/prototype-overview.html +256 -0
- package/.worktrees/8d2d0392/packages/dashboard/package.json +25 -0
- package/.worktrees/8d2d0392/packages/dashboard/server/executor.js +95 -0
- package/.worktrees/8d2d0392/packages/dashboard/server/index.js +679 -0
- package/.worktrees/8d2d0392/packages/dashboard/server/parser.js +526 -0
- package/.worktrees/8d2d0392/packages/dashboard/server/watcher.js +357 -0
- package/.worktrees/8d2d0392/src/agent-session-log.js +803 -0
- package/.worktrees/8d2d0392/src/change-list.js +290 -0
- package/.worktrees/8d2d0392/src/change-risk-profile.js +339 -0
- package/.worktrees/8d2d0392/src/check-primitives.js +65 -0
- package/.worktrees/8d2d0392/src/classify-change.js +109 -0
- package/.worktrees/8d2d0392/src/commit-suggest.js +202 -0
- package/.worktrees/8d2d0392/src/config-cat.js +88 -0
- package/.worktrees/8d2d0392/src/config-schema.js +328 -0
- package/.worktrees/8d2d0392/src/constants.js +97 -0
- package/.worktrees/8d2d0392/src/contract-matrix.js +533 -0
- package/.worktrees/8d2d0392/src/datetime.js +20 -0
- package/.worktrees/8d2d0392/src/db-engine.js +55 -0
- package/.worktrees/8d2d0392/src/db.js +381 -0
- package/.worktrees/8d2d0392/src/decision-distill.js +517 -0
- package/.worktrees/8d2d0392/src/dispatch/backends/local-agent.js +155 -0
- package/.worktrees/8d2d0392/src/dispatch/backends/sillyhub-mcp.js +245 -0
- package/.worktrees/8d2d0392/src/dispatch/probe.js +246 -0
- package/.worktrees/8d2d0392/src/dispatch/strategy.js +121 -0
- package/.worktrees/8d2d0392/src/docs-check.js +825 -0
- package/.worktrees/8d2d0392/src/docs-debt.js +393 -0
- package/.worktrees/8d2d0392/src/docs-gate.js +107 -0
- package/.worktrees/8d2d0392/src/doctor-diagnostics.js +1099 -0
- package/.worktrees/8d2d0392/src/endpoint-extractor.js +409 -0
- package/.worktrees/8d2d0392/src/foreign-declared.js +78 -0
- package/.worktrees/8d2d0392/src/fs-atomic.js +73 -0
- package/.worktrees/8d2d0392/src/git-helper.js +150 -0
- package/.worktrees/8d2d0392/src/hooks/claude-pre-tool-use.cjs +130 -0
- package/.worktrees/8d2d0392/src/hooks/worktree-guard.js +892 -0
- package/.worktrees/8d2d0392/src/index.js +2854 -0
- package/.worktrees/8d2d0392/src/init.js +626 -0
- package/.worktrees/8d2d0392/src/knowledge-match.js +231 -0
- package/.worktrees/8d2d0392/src/local-detect.js +128 -0
- package/.worktrees/8d2d0392/src/local-register.js +104 -0
- package/.worktrees/8d2d0392/src/machine-interface.js +543 -0
- package/.worktrees/8d2d0392/src/migrate.js +127 -0
- package/.worktrees/8d2d0392/src/module-impact.js +166 -0
- package/.worktrees/8d2d0392/src/module-resolve.js +222 -0
- package/.worktrees/8d2d0392/src/modules.js +580 -0
- package/.worktrees/8d2d0392/src/plan-adopt-waves.js +126 -0
- package/.worktrees/8d2d0392/src/progress/change-registry.js +336 -0
- package/.worktrees/8d2d0392/src/progress/consistency-doctor.js +355 -0
- package/.worktrees/8d2d0392/src/progress/shared.js +36 -0
- package/.worktrees/8d2d0392/src/progress/stage-machine.js +594 -0
- package/.worktrees/8d2d0392/src/progress/step-store.js +265 -0
- package/.worktrees/8d2d0392/src/progress.js +1304 -0
- package/.worktrees/8d2d0392/src/quick-recommend.js +115 -0
- package/.worktrees/8d2d0392/src/quicklog.js +883 -0
- package/.worktrees/8d2d0392/src/review-tier.js +77 -0
- package/.worktrees/8d2d0392/src/run/command.js +1641 -0
- package/.worktrees/8d2d0392/src/run/complete-handlers.js +1552 -0
- package/.worktrees/8d2d0392/src/run/complete.js +1259 -0
- package/.worktrees/8d2d0392/src/run/concurrent-detect.js +156 -0
- package/.worktrees/8d2d0392/src/run/gates.js +1094 -0
- package/.worktrees/8d2d0392/src/run/multi-repo-context.js +208 -0
- package/.worktrees/8d2d0392/src/run/next.js +128 -0
- package/.worktrees/8d2d0392/src/run/prompt.js +843 -0
- package/.worktrees/8d2d0392/src/run/quick-audit.js +186 -0
- package/.worktrees/8d2d0392/src/run/scan-profile.js +269 -0
- package/.worktrees/8d2d0392/src/run/shared.js +1562 -0
- package/.worktrees/8d2d0392/src/run/stage.js +587 -0
- package/.worktrees/8d2d0392/src/run.js +23 -0
- package/.worktrees/8d2d0392/src/scan-diff.js +388 -0
- package/.worktrees/8d2d0392/src/scan-postcheck.js +524 -0
- package/.worktrees/8d2d0392/src/scan-staleness.js +95 -0
- package/.worktrees/8d2d0392/src/setup.js +414 -0
- package/.worktrees/8d2d0392/src/sillyhub-mcp/client.js +597 -0
- package/.worktrees/8d2d0392/src/sillyhub-mcp/config.js +62 -0
- package/.worktrees/8d2d0392/src/spec-dir-typo.js +50 -0
- package/.worktrees/8d2d0392/src/spec-sync.js +358 -0
- package/.worktrees/8d2d0392/src/stage-contract-engine.js +171 -0
- package/.worktrees/8d2d0392/src/stage-contract-spec.js +551 -0
- package/.worktrees/8d2d0392/src/stage-contract.js +948 -0
- package/.worktrees/8d2d0392/src/stage-review.js +597 -0
- package/.worktrees/8d2d0392/src/stages/archive.js +199 -0
- package/.worktrees/8d2d0392/src/stages/brainstorm-auto.js +285 -0
- package/.worktrees/8d2d0392/src/stages/brainstorm.js +624 -0
- package/.worktrees/8d2d0392/src/stages/cmd-existence.js +145 -0
- package/.worktrees/8d2d0392/src/stages/doctor.js +540 -0
- package/.worktrees/8d2d0392/src/stages/execute.js +1277 -0
- package/.worktrees/8d2d0392/src/stages/explore.js +43 -0
- package/.worktrees/8d2d0392/src/stages/index.js +29 -0
- package/.worktrees/8d2d0392/src/stages/knowledge.js +514 -0
- package/.worktrees/8d2d0392/src/stages/plan-postcheck.js +1398 -0
- package/.worktrees/8d2d0392/src/stages/plan.js +651 -0
- package/.worktrees/8d2d0392/src/stages/quick.js +167 -0
- package/.worktrees/8d2d0392/src/stages/scan.js +608 -0
- package/.worktrees/8d2d0392/src/stages/status.js +64 -0
- package/.worktrees/8d2d0392/src/stages/verify.js +246 -0
- package/.worktrees/8d2d0392/src/sync.js +1470 -0
- package/.worktrees/8d2d0392/src/task-review.js +1498 -0
- package/.worktrees/8d2d0392/src/taskcard-placeholders.js +21 -0
- package/.worktrees/8d2d0392/src/taskcard.js +217 -0
- package/.worktrees/8d2d0392/src/verify-postcheck.js +1718 -0
- package/.worktrees/8d2d0392/src/verify-probes.js +327 -0
- package/.worktrees/8d2d0392/src/version.js +19 -0
- package/.worktrees/8d2d0392/src/workflow.js +600 -0
- package/.worktrees/8d2d0392/src/workspace.js +125 -0
- package/.worktrees/8d2d0392/src/worktree-apply.js +1611 -0
- package/.worktrees/8d2d0392/src/worktree-deps.js +576 -0
- package/.worktrees/8d2d0392/src/worktree.js +1838 -0
- package/.worktrees/8d2d0392/templates/claude-instruction.md +23 -0
- package/.worktrees/8d2d0392/templates/prompts/taskcard-rules.md +29 -0
- package/.worktrees/8d2d0392/templates/prompts/testcase-design.md +12 -0
- package/.worktrees/8d2d0392/templates/prompts/verify-probes.md +32 -0
- package/.worktrees/8d2d0392/templates/workflows/archive-impact.yaml +79 -0
- package/.worktrees/8d2d0392/templates/workflows/scan-docs.yaml +132 -0
- package/architecture-analysis.md +448 -0
- package/eval/README.md +86 -0
- package/eval/eval.config.json +28 -0
- package/eval/lib/adapters/cli.mjs +63 -0
- package/eval/lib/adapters/dryrun.mjs +27 -0
- package/eval/lib/config.mjs +82 -0
- package/eval/lib/db.mjs +76 -0
- package/eval/lib/report.mjs +120 -0
- package/eval/lib/runner.mjs +157 -0
- package/eval/lib/tasks.mjs +73 -0
- package/eval/lib/util.mjs +107 -0
- package/eval/lib/workdir.mjs +30 -0
- package/eval/run.mjs +83 -0
- package/eval/tasks/001-slugify/files/package.json +6 -0
- package/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
- package/eval/tasks/001-slugify/task.json +6 -0
- package/eval/tasks/001-slugify/verify.mjs +28 -0
- package/eval/tasks/002-each-day/files/package.json +6 -0
- package/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
- package/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
- package/eval/tasks/002-each-day/task.json +6 -0
- package/eval/tasks/002-each-day/verify.mjs +31 -0
- package/eval/tasks/003-word-report/files/data/notes.txt +5 -0
- package/eval/tasks/003-word-report/files/package.json +6 -0
- package/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
- package/eval/tasks/003-word-report/task.json +6 -0
- package/eval/tasks/003-word-report/verify.mjs +38 -0
- package/package.json +4 -2
- package/src/agent-session-log.js +45 -5
- package/src/change-risk-profile.js +83 -14
- package/src/config-schema.js +18 -3
- package/src/contract-matrix.js +90 -29
- package/src/datetime.js +20 -0
- package/src/decision-distill.js +517 -0
- package/src/dispatch/backends/local-agent.js +4 -4
- package/src/docs-check.js +227 -0
- package/src/docs-debt.js +187 -24
- package/src/doctor-diagnostics.js +102 -2
- package/src/endpoint-extractor.js +41 -52
- package/src/foreign-declared.js +155 -0
- package/src/index.js +95 -13
- package/src/init.js +9 -7
- package/src/knowledge-match.js +107 -6
- package/src/local-register.js +7 -1
- package/src/module-resolve.js +12 -1
- package/src/modules.js +2 -1
- package/src/plan-adopt-waves.js +126 -0
- package/src/quicklog.js +50 -4
- package/src/run/command.js +124 -44
- package/src/run/complete-handlers.js +48 -19
- package/src/run/complete.js +5 -2
- package/src/run/gates.js +104 -22
- package/src/run/prompt.js +84 -42
- package/src/run/quick-audit.js +11 -0
- package/src/run/shared.js +152 -10
- package/src/run/stage.js +8 -5
- package/src/run.js +2 -2
- package/src/scan-postcheck.js +30 -17
- package/src/stage-contract-spec.js +3 -2
- package/src/stage-contract.js +68 -4
- package/src/stages/archive.js +53 -12
- package/src/stages/brainstorm-auto.js +50 -1
- package/src/stages/brainstorm.js +13 -0
- package/src/stages/doctor.js +157 -1
- package/src/stages/execute.js +32 -6
- package/src/stages/plan-postcheck.js +76 -17
- package/src/stages/plan.js +9 -4
- package/src/stages/quick.js +6 -1
- package/src/stages/verify.js +32 -15
- package/src/sync.js +41 -3
- package/src/task-review.js +2 -2
- package/src/taskcard-placeholders.js +21 -0
- package/src/taskcard.js +43 -1
- package/src/verify-postcheck.js +315 -13
- package/src/verify-probes.js +73 -10
- package/src/worktree-apply.js +569 -227
- package/src/worktree-deps.js +150 -1
- package/src/worktree.js +285 -77
- package/templates/prompts/taskcard-rules.md +2 -1
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-session-log.js — 本地 agent 会话日志路径探测 + 上报平台(多 harness)
|
|
3
|
+
*
|
|
4
|
+
* 背景:平台模式(SillyHub daemon 调 CLI)下,平台会话只能看到 CLI 内部阶段信息,
|
|
5
|
+
* 看不到本地 agent 的实际执行日志——agent 的完整模型 I/O / 工具调用 / 输出都在各
|
|
6
|
+
* agent CLI 本地的会话日志里(如 Codex rollout、Claude Code transcript、ZCode
|
|
7
|
+
* model-io),平台无从得知。
|
|
8
|
+
*
|
|
9
|
+
* 方案(docs/platform-agent-log-protocol.md):CLI 在 run 命令入口探测「当前正被哪个
|
|
10
|
+
* agent CLI 驱动」,把 agent **完整会话日志**的本地绝对路径**主动 REST 上报**到平台
|
|
11
|
+
* (POST /api/agent-logs,与进度上报同风格;平台端落库后在会话视图展示,再按路径让
|
|
12
|
+
* daemon 读本地文件解析内容)。同时写本地产物 <runtimeRoot>/agent-session-log.json
|
|
13
|
+
* 留底(上报失败兜底 + `sillyspec agent-log` 查询源)。
|
|
14
|
+
*
|
|
15
|
+
* 支持的 harness(探测规则全部实证——本机布局 + CLI 包源码/二进制,见 protocol 文档):
|
|
16
|
+
* - claude-code:env CLAUDECODE / CLAUDE_CODE_ENTRYPOINT 门控;
|
|
17
|
+
* <home>/.claude/projects/<slug-cwd>/<sessionId>.jsonl(slug = 路径非字母数字全替 '-')
|
|
18
|
+
* - codex:无可靠 env 标记,按 mtime 活跃窗口扫 <home>/.codex/sessions/YYYY/MM/DD/
|
|
19
|
+
* rollout-*.jsonl,读首行 session_meta.cwd 精确匹配 CLI cwd(并带出 originator,
|
|
20
|
+
* 如 sillyhub-daemon,供平台关联派发来源)
|
|
21
|
+
* - zcode:env ZCODE_* 门控;<home>/.zcode/cli/rollout/model-io-sess_<id>.jsonl
|
|
22
|
+
* - pi:目录名即 cwd 编码(--<路径非字母数字替'-'>--),无需 env 门控;
|
|
23
|
+
* <home>/.pi/agent/sessions/<safePath>/session.jsonl
|
|
24
|
+
* - deepseek-dsh(@deepseek-ai/dsh):同 pi 构但 ':' 删除;
|
|
25
|
+
* <home>/.dsh/sessions/<safePath>/session-<uuid>/session.jsonl.zstd(zstd 压缩)
|
|
26
|
+
* - cursor / opencode:loose 探测器(无 cwd/env 归属线索,仅 precise 全落空时启用)
|
|
27
|
+
* - 其他 CLI:env SILLYSPEC_AGENT_LOG 显式指定(绝对路径)
|
|
28
|
+
*
|
|
29
|
+
* 上报配置来源(优先级):env SILLYHUB_PLATFORM_URL + SILLYHUB_PLATFORM_TOKEN(daemon
|
|
30
|
+
* 注入通道)> local.yaml platform 段(与进度同步同源)。**不受平台模式 sentinel 限制**
|
|
31
|
+
* (链路 A 进度同步在平台模式跳过是因为 daemon 自有进度回传链路;agent 日志没有
|
|
32
|
+
* daemon 链路,本上报就是它的主通道)。env SILLYSPEC_AGENT_LOG_PUSH=0 可关。
|
|
33
|
+
*
|
|
34
|
+
* 铁律:全程 best-effort——探测不到不发不写、任何失败只 warn 一行绝不阻断 run 主流程。
|
|
35
|
+
* 只上报路径 + 元信息(session_id/format/originator),不读日志内容(解析归平台侧),
|
|
36
|
+
* 不引入新依赖。
|
|
37
|
+
*/
|
|
38
|
+
import { existsSync, readdirSync, statSync, readFileSync, mkdirSync, openSync, readSync, closeSync } from 'fs';
|
|
39
|
+
import { join, isAbsolute } from 'path';
|
|
40
|
+
import { homedir } from 'os';
|
|
41
|
+
import { writeAtomicSync } from './fs-atomic.js';
|
|
42
|
+
import { withFileLock } from './quicklog.js';
|
|
43
|
+
import { resolveRuntimeRoot } from './run/shared.js';
|
|
44
|
+
|
|
45
|
+
export const AGENT_LOG_SCHEMA_VERSION = 1;
|
|
46
|
+
export const AGENT_LOG_ARTIFACT_FILENAME = 'agent-session-log.json';
|
|
47
|
+
/** daemon/用户显式指定 agent 日志路径的 env 名(绝对路径;相对路径忽略)。 */
|
|
48
|
+
export const AGENT_LOG_ENV_OVERRIDE = 'SILLYSPEC_AGENT_LOG';
|
|
49
|
+
|
|
50
|
+
// 自动探测只认「活跃窗口」内被写过的会话文件:CLI 此刻正被 agent 驱动,当前会话日志的
|
|
51
|
+
// mtime 必然新鲜;窗口外的旧文件(上周的会话)不登记,防平台解析到错误日志。
|
|
52
|
+
// env 覆盖不受此窗口限制(显式指定 = 调用方负责正确性)。
|
|
53
|
+
const ACTIVE_WINDOW_MS = 15 * 60 * 1000;
|
|
54
|
+
// 单 harness 单次探测的条目上限(并行 Wave 会同时开多个子代理会话,都算 agent 日志)。
|
|
55
|
+
const MAX_PER_HARNESS = 6;
|
|
56
|
+
// 产物 entries 上限(按 last_seen_at 新→旧保留;超出淘汰最旧)。
|
|
57
|
+
const MAX_ENTRIES = 10;
|
|
58
|
+
// 产物合并锁超时:登记是附带动作,锁竞争超时直接放弃本次(下次 run 自愈)。
|
|
59
|
+
const MERGE_LOCK_TIMEOUT_MS = 2_000;
|
|
60
|
+
// 上报超时(与 quicklog 推送同量级:best-effort,不拖 run 主流程)。
|
|
61
|
+
const PUSH_TIMEOUT_MS = 5_000;
|
|
62
|
+
|
|
63
|
+
function debugLog(msg) {
|
|
64
|
+
if (process.env.SILLYSPEC_DEBUG_AGENT_LOG || process.env.SILLYSPEC_DEBUG_SYNC) {
|
|
65
|
+
console.warn(`[agent-log] ${msg}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** 路径归一正斜杠(跨平台产物契约:Windows 反斜杠不出现在 JSON 产物里)。 */
|
|
70
|
+
function toPosix(p) {
|
|
71
|
+
return String(p).replace(/\\/g, '/');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** cwd 等值比较口径:正斜杠归一 + Windows 形态路径大小写不敏感。
|
|
75
|
+
* 按数据形态判而非宿主平台——平台 hub 场景 daemon 跑在任意平台,都要能与 Windows
|
|
76
|
+
* agent 上报的 cwd(盘符 `c:/x` 或 UNC `\\server\share`)等值比较;toPosix 在前,
|
|
77
|
+
* UNC 的反斜杠此时已成 `//`。POSIX 形态路径保持大小写敏感(文件系统语义如此)。 */
|
|
78
|
+
function normCwd(p) {
|
|
79
|
+
const s = toPosix(p);
|
|
80
|
+
const winForm = /^[a-zA-Z]:\//.test(s) || s.startsWith('//');
|
|
81
|
+
return winForm ? s.toLowerCase() : s;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function cwdsMatch(a, b) {
|
|
85
|
+
return Boolean(a) && Boolean(b) && normCwd(a) === normCwd(b);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function statSafe(p) {
|
|
89
|
+
try { return statSync(p); } catch { return null; }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 会话化上下文 id 消毒(2026-08-23-agent-activity-sessions):非空字符串 trim 后返回,
|
|
94
|
+
* 其余(undefined/null/空白)返回 null——best-effort 语义:ctx 解析失败不带、不抛错。
|
|
95
|
+
* 只用于标识类值(hubSessionId / changeKey / quickId),非 flag 值。
|
|
96
|
+
*/
|
|
97
|
+
function pickCtxId(v) {
|
|
98
|
+
if (typeof v !== 'string') return null;
|
|
99
|
+
const t = v.trim();
|
|
100
|
+
return t || null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 读文件首行并 JSON.parse(只读前 maxBytes 字节,防大日志整读)。
|
|
105
|
+
* codex rollout 首行是 session_meta(含 cwd/originator),这是 cwd 精确匹配的数据源。
|
|
106
|
+
* @returns {object|null} 解析失败/文件不可读返回 null
|
|
107
|
+
*/
|
|
108
|
+
function readFirstLineJson(filePath, maxBytes = 16384) {
|
|
109
|
+
let fd = null;
|
|
110
|
+
try {
|
|
111
|
+
fd = openSync(filePath, 'r');
|
|
112
|
+
const buf = Buffer.alloc(maxBytes);
|
|
113
|
+
const n = readSync(fd, buf, 0, maxBytes, 0);
|
|
114
|
+
const text = buf.subarray(0, n).toString('utf8');
|
|
115
|
+
const nl = text.indexOf('\n');
|
|
116
|
+
return JSON.parse(nl === -1 ? text : text.slice(0, nl));
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
} finally {
|
|
120
|
+
if (fd !== null) { try { closeSync(fd) } catch { /* best effort */ } }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 活跃文件收集:dir 内 suffix 结尾文件,mtime 在窗口内,mtime 新→旧
|
|
125
|
+
function listActiveFiles(dir, { now, windowMs, limit }) {
|
|
126
|
+
let names;
|
|
127
|
+
try {
|
|
128
|
+
names = readdirSync(dir).filter(f => f.endsWith('.jsonl'));
|
|
129
|
+
} catch {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
const active = [];
|
|
133
|
+
for (const name of names) {
|
|
134
|
+
const full = join(dir, name);
|
|
135
|
+
const st = statSafe(full);
|
|
136
|
+
if (!st || now - st.mtimeMs > windowMs) continue;
|
|
137
|
+
active.push({ name, full, mtimeMs: st.mtimeMs });
|
|
138
|
+
}
|
|
139
|
+
active.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
140
|
+
return active.slice(0, limit);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Claude Code 项目目录名 munging 规则:绝对路径所有非字母数字字符替换为 '-'。
|
|
145
|
+
* 实证(本机 ~/.claude/projects/):
|
|
146
|
+
* C:\Users\qinyi\IdeaProjects\sillyspec → C--Users-qinyi-IdeaProjects-sillyspec
|
|
147
|
+
* C:\Users\qinyi\IdeaProjects\sillyspec\.worktrees\8f1 → C--Users-qinyi-IdeaProjects-sillyspec--worktrees-8f1
|
|
148
|
+
*/
|
|
149
|
+
export function mungeClaudeProjectDir(cwd) {
|
|
150
|
+
return String(cwd).replace(/[^a-zA-Z0-9]/g, '-');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── harness 探测器注册表(新增 CLI = 加一个探测器对象 + protocol 文档补格式说明)──
|
|
154
|
+
|
|
155
|
+
/** Claude Code:env 门控(CLAUDECODE / CLAUDE_CODE_ENTRYPOINT)+ 项目 slug 目录精确到 cwd。 */
|
|
156
|
+
function detectClaudeCode(ctx) {
|
|
157
|
+
const { cwdCandidates, env, homeDir, now, windowMs } = ctx;
|
|
158
|
+
if (!(env.CLAUDECODE === '1' || (typeof env.CLAUDE_CODE_ENTRYPOINT === 'string' && env.CLAUDE_CODE_ENTRYPOINT))) {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
const claudeRoot = env.CLAUDE_CONFIG_DIR || join(homeDir, '.claude');
|
|
162
|
+
const projectsRoot = join(claudeRoot, 'projects');
|
|
163
|
+
const entries = [];
|
|
164
|
+
for (const rawCwd of cwdCandidates) {
|
|
165
|
+
if (!rawCwd) continue;
|
|
166
|
+
const projectDir = join(projectsRoot, mungeClaudeProjectDir(rawCwd));
|
|
167
|
+
if (!existsSync(projectDir)) continue;
|
|
168
|
+
|
|
169
|
+
// CLAUDE_SESSION_ID 精确匹配(存在则必登):比 newest-mtime 猜测可靠
|
|
170
|
+
const sessionId = typeof env.CLAUDE_SESSION_ID === 'string' ? env.CLAUDE_SESSION_ID.trim() : '';
|
|
171
|
+
if (sessionId) {
|
|
172
|
+
const exact = join(projectDir, `${sessionId}.jsonl`);
|
|
173
|
+
if (existsSync(exact)) {
|
|
174
|
+
entries.push({
|
|
175
|
+
harness: 'claude-code', format: 'claude-code-jsonl', detected_via: 'claude-code-session-id',
|
|
176
|
+
log_path: toPosix(exact), agent_cwd: toPosix(rawCwd),
|
|
177
|
+
session_id: sessionId, originator: null,
|
|
178
|
+
mtime_ms: statSafe(exact)?.mtimeMs ?? null,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (const f of listActiveFiles(projectDir, { now, windowMs, limit: MAX_PER_HARNESS })) {
|
|
184
|
+
const posix = toPosix(f.full);
|
|
185
|
+
if (entries.some(e => e.log_path === posix)) continue;
|
|
186
|
+
entries.push({
|
|
187
|
+
harness: 'claude-code', format: 'claude-code-jsonl', detected_via: 'claude-code-active-scan',
|
|
188
|
+
log_path: posix, agent_cwd: toPosix(rawCwd),
|
|
189
|
+
session_id: f.name.replace(/\.jsonl$/, ''), originator: null,
|
|
190
|
+
mtime_ms: f.mtimeMs,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return entries;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Codex:无可靠子进程 env 标记(daemon 拉起场景实证),按日期目录扫活跃 rollout,
|
|
199
|
+
* 读首行 session_meta.cwd 精确匹配。originator(如 sillyhub-daemon)一并带出供平台关联。
|
|
200
|
+
* 布局实证:<home>/.codex/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl,首行
|
|
201
|
+
* {"type":"session_meta","payload":{"session_id","cwd","originator","cli_version",...}}。
|
|
202
|
+
*/
|
|
203
|
+
function detectCodex(ctx) {
|
|
204
|
+
const { cwdCandidates, env, homeDir, now, windowMs } = ctx;
|
|
205
|
+
const codexHome = env.CODEX_HOME || join(homeDir, '.codex');
|
|
206
|
+
const sessionsRoot = join(codexHome, 'sessions');
|
|
207
|
+
if (!existsSync(sessionsRoot)) return [];
|
|
208
|
+
|
|
209
|
+
// 只扫今天/昨天两个日期目录(窗口 15min 跨零点最多涉及两天;目录按日期分层是天然的扫描剪枝)
|
|
210
|
+
const dayDirs = [new Date(now), new Date(now - 24 * 60 * 60 * 1000)]
|
|
211
|
+
.map(d => join(sessionsRoot, String(d.getFullYear()), String(d.getMonth() + 1).padStart(2, '0'), String(d.getDate()).padStart(2, '0')));
|
|
212
|
+
|
|
213
|
+
const entries = [];
|
|
214
|
+
for (const dir of [...new Set(dayDirs)]) {
|
|
215
|
+
for (const f of listActiveFiles(dir, { now, windowMs, limit: MAX_PER_HARNESS })) {
|
|
216
|
+
const meta = readFirstLineJson(f.full);
|
|
217
|
+
const payload = meta && meta.type === 'session_meta' ? (meta.payload || {}) : {};
|
|
218
|
+
// cwd 精确匹配:防并发其他项目的 codex 会话串台(session_meta.cwd 与 CLI cwd 等值才登记)
|
|
219
|
+
if (!cwdCandidates.some(c => c && payload.cwd && cwdsMatch(c, payload.cwd))) continue;
|
|
220
|
+
entries.push({
|
|
221
|
+
harness: 'codex', format: 'codex-rollout-jsonl', detected_via: 'codex-session-meta-cwd',
|
|
222
|
+
log_path: toPosix(f.full), agent_cwd: toPosix(payload.cwd),
|
|
223
|
+
session_id: typeof payload.session_id === 'string' ? payload.session_id : null,
|
|
224
|
+
originator: typeof payload.originator === 'string' ? payload.originator : null,
|
|
225
|
+
mtime_ms: f.mtimeMs,
|
|
226
|
+
});
|
|
227
|
+
if (entries.length >= MAX_PER_HARNESS) break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return entries;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* ZCode:env ZCODE_* 门控(本机实证子进程可见 ZCODE_APP_VERSION/ZCODE_ENV 等);
|
|
235
|
+
* <home>/.zcode/cli/rollout/model-io-sess_<id>.jsonl(含 subagent 会话,文件名带
|
|
236
|
+
* sess_subagent_agent_ 前缀),完整模型 I/O。无 cwd 字段 → env 门控即归属依据。
|
|
237
|
+
*/
|
|
238
|
+
function detectZcode(ctx) {
|
|
239
|
+
const { cwdCandidates, env, homeDir, now, windowMs } = ctx;
|
|
240
|
+
const inZcode = Boolean(env.ZCODE_APP_VERSION || env.ZCODE_ENV || env.ZCODE_RUNTIME_ENV);
|
|
241
|
+
if (!inZcode) return [];
|
|
242
|
+
const rolloutDir = join(homeDir, '.zcode', 'cli', 'rollout');
|
|
243
|
+
const cwdPosix = toPosix(cwdCandidates[0] || '');
|
|
244
|
+
return listActiveFiles(rolloutDir, { now, windowMs, limit: MAX_PER_HARNESS }).map(f => ({
|
|
245
|
+
harness: 'zcode', format: 'zcode-model-io-jsonl', detected_via: 'zcode-env-marker',
|
|
246
|
+
log_path: toPosix(f.full), agent_cwd: cwdPosix,
|
|
247
|
+
session_id: f.name.replace(/^model-io-sess_/, '').replace(/\.jsonl$/, ''),
|
|
248
|
+
originator: null,
|
|
249
|
+
mtime_ms: f.mtimeMs,
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* pi(@earendil-works/pi-coding-agent):完整会话 = <home>/.pi/agent/sessions/<safePath>/session.jsonl。
|
|
255
|
+
* safePath 编码规则(dist/migrations.js:101 源码实证):
|
|
256
|
+
* `--${cwd.replace(/^[/\\]/,'').replace(/[/\\:]/g,'-')}--`
|
|
257
|
+
* 实证:C:\Users\qinyi\IdeaProjects\multi-agent-platform → --C--Users-qinyi-IdeaProjects-multi-agent-platform--
|
|
258
|
+
* 目录名即 cwd 编码 → 天然精确归属,无需 env 门控。
|
|
259
|
+
*/
|
|
260
|
+
export function mungePiSafePath(cwd) {
|
|
261
|
+
return `--${String(cwd).replace(/^[/\\]/, '').replace(/[/\\:]/g, '-')}--`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function detectPi(ctx) {
|
|
265
|
+
const { cwdCandidates, homeDir, now, windowMs } = ctx;
|
|
266
|
+
const entries = [];
|
|
267
|
+
for (const rawCwd of cwdCandidates) {
|
|
268
|
+
if (!rawCwd) continue;
|
|
269
|
+
const sessionFile = join(homeDir, '.pi', 'agent', 'sessions', mungePiSafePath(rawCwd), 'session.jsonl');
|
|
270
|
+
const st = statSafe(sessionFile);
|
|
271
|
+
if (!st || now - st.mtimeMs > windowMs) continue;
|
|
272
|
+
entries.push({
|
|
273
|
+
harness: 'pi', format: 'pi-session-jsonl', detected_via: 'pi-safe-path',
|
|
274
|
+
log_path: toPosix(sessionFile), agent_cwd: toPosix(rawCwd),
|
|
275
|
+
session_id: null, originator: null,
|
|
276
|
+
mtime_ms: st.mtimeMs,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return entries;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* dsh(@deepseek-ai/dsh,DeepSeek agent CLI):完整会话 =
|
|
284
|
+
* <home>/.dsh/sessions/<safePath>/session-<uuid>/session.jsonl.zstd(zstd 压缩 jsonl)。
|
|
285
|
+
* safePath 编码(sessions/ 目录名 + storages/session_projcache.json 的 cwd 字段交叉实证):
|
|
286
|
+
* 与 pi 同构但 `:` 是删除而非替换 —— `--${cwd 去首斜杠、删 ':'、[/\\]→'-'}--`。
|
|
287
|
+
* 实证:C:\Users\qinyi\IdeaProjects\multi-agent-platform → --C-Users-qinyi-IdeaProjects-multi-agent-platform--
|
|
288
|
+
*/
|
|
289
|
+
export function mungeDshSafePath(cwd) {
|
|
290
|
+
return `--${String(cwd).replace(/^[/\\]/, '').replace(/:/g, '').replace(/[/\\]/g, '-')}--`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function detectDsh(ctx) {
|
|
294
|
+
const { cwdCandidates, homeDir, now, windowMs } = ctx;
|
|
295
|
+
const entries = [];
|
|
296
|
+
for (const rawCwd of cwdCandidates) {
|
|
297
|
+
if (!rawCwd) continue;
|
|
298
|
+
const projectDir = join(homeDir, '.dsh', 'sessions', mungeDshSafePath(rawCwd));
|
|
299
|
+
if (!existsSync(projectDir)) continue;
|
|
300
|
+
// 会话目录(session-<uuid>/)按 mtime 活跃窗口取最新几个,完整日志 = 目录内 session.jsonl.zstd
|
|
301
|
+
let sessionDirs;
|
|
302
|
+
try {
|
|
303
|
+
sessionDirs = readdirSync(projectDir, { withFileTypes: true }).filter(d => d.isDirectory()).map(d => d.name);
|
|
304
|
+
} catch { continue; }
|
|
305
|
+
const active = [];
|
|
306
|
+
for (const name of sessionDirs) {
|
|
307
|
+
const logFile = join(projectDir, name, 'session.jsonl.zstd');
|
|
308
|
+
const st = statSafe(logFile);
|
|
309
|
+
if (!st || now - st.mtimeMs > windowMs) continue;
|
|
310
|
+
active.push({ name, logFile, mtimeMs: st.mtimeMs });
|
|
311
|
+
}
|
|
312
|
+
active.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
313
|
+
for (const a of active.slice(0, MAX_PER_HARNESS)) {
|
|
314
|
+
entries.push({
|
|
315
|
+
harness: 'deepseek-dsh', format: 'dsh-session-jsonl-zstd', detected_via: 'dsh-safe-path',
|
|
316
|
+
log_path: toPosix(a.logFile), agent_cwd: toPosix(rawCwd),
|
|
317
|
+
session_id: a.name.replace(/^session-/, ''), originator: null,
|
|
318
|
+
mtime_ms: a.mtimeMs,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return entries;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Cursor(IDE / cursor-agent CLI):完整会话 = <home>/.cursor/chats/<workspaceHash>/<chatUuid>/
|
|
327
|
+
* {meta.json, store.db(sqlite)}。无 cwd 映射(workspaceHash 算法未知)也无子进程 env 标记
|
|
328
|
+
* → 宽探测器(loose):仅按 store.db(+wal) mtime 活跃窗口登记,且只在所有精确探测器
|
|
329
|
+
* 落空时启用(防 Cursor IDE 日常使用在其他项目聊天时误报为当前 agent 日志)。
|
|
330
|
+
*/
|
|
331
|
+
function detectCursor(ctx) {
|
|
332
|
+
const { cwdCandidates, homeDir, now, windowMs } = ctx;
|
|
333
|
+
const chatsRoot = join(homeDir, '.cursor', 'chats');
|
|
334
|
+
let wsDirs;
|
|
335
|
+
try {
|
|
336
|
+
wsDirs = readdirSync(chatsRoot, { withFileTypes: true }).filter(d => d.isDirectory());
|
|
337
|
+
} catch { return []; }
|
|
338
|
+
const cwdPosix = toPosix(cwdCandidates[0] || '');
|
|
339
|
+
const active = [];
|
|
340
|
+
for (const ws of wsDirs) {
|
|
341
|
+
let chatDirs;
|
|
342
|
+
try {
|
|
343
|
+
chatDirs = readdirSync(join(chatsRoot, ws.name), { withFileTypes: true }).filter(d => d.isDirectory());
|
|
344
|
+
} catch { continue; }
|
|
345
|
+
for (const chat of chatDirs) {
|
|
346
|
+
const chatDir = join(chatsRoot, ws.name, chat.name);
|
|
347
|
+
// sqlite WAL 模式下写入打在 -wal 侧车,取两者 mtime 较大者判活跃
|
|
348
|
+
const db = statSafe(join(chatDir, 'store.db'));
|
|
349
|
+
const wal = statSafe(join(chatDir, 'store.db-wal'));
|
|
350
|
+
const st = db || wal;
|
|
351
|
+
if (!st) continue;
|
|
352
|
+
const mtime = Math.max(db?.mtimeMs ?? 0, wal?.mtimeMs ?? 0);
|
|
353
|
+
if (now - mtime > windowMs) continue;
|
|
354
|
+
active.push({ chatDir, sessionId: chat.name, mtimeMs: mtime });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
active.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
358
|
+
return active.slice(0, 3).map(a => ({
|
|
359
|
+
harness: 'cursor', format: 'cursor-chat-sqlite', detected_via: 'cursor-recent-scan',
|
|
360
|
+
log_path: toPosix(a.chatDir), agent_cwd: cwdPosix,
|
|
361
|
+
session_id: a.sessionId, originator: null,
|
|
362
|
+
mtime_ms: a.mtimeMs,
|
|
363
|
+
}));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* opencode(opencode-ai v1.17+):完整会话 = <dataRoot>/opencode/storage/session/ 下的
|
|
368
|
+
* JSON 树(info/<id>.json 会话元信息 + message/ + part/ 全量内容;二进制字符串实证布局)。
|
|
369
|
+
* dataRoot = $XDG_DATA_HOME || ~/.local/share。cwd 归属:读活跃窗口内 info/*.json 做
|
|
370
|
+
* cwd 文本包含匹配(对 JSON 形状宽容);无匹配不登记。宽探测器(loose,布局未本机实证,
|
|
371
|
+
* 生产不命中时静默 no-op,env 覆盖兜底)。
|
|
372
|
+
*/
|
|
373
|
+
function detectOpencode(ctx) {
|
|
374
|
+
const { cwdCandidates, env, homeDir, now, windowMs } = ctx;
|
|
375
|
+
const dataRoot = env.XDG_DATA_HOME || join(homeDir, '.local', 'share');
|
|
376
|
+
const sessionRoot = join(dataRoot, 'opencode', 'storage', 'session');
|
|
377
|
+
const infoDir = join(sessionRoot, 'info');
|
|
378
|
+
let infoFiles;
|
|
379
|
+
try {
|
|
380
|
+
infoFiles = readdirSync(infoDir).filter(f => f.endsWith('.json'));
|
|
381
|
+
} catch { return []; }
|
|
382
|
+
const cwdPosix = toPosix(cwdCandidates[0] || '');
|
|
383
|
+
const active = [];
|
|
384
|
+
for (const f of infoFiles) {
|
|
385
|
+
const full = join(infoDir, f);
|
|
386
|
+
const st = statSafe(full);
|
|
387
|
+
if (!st || now - st.mtimeMs > windowMs) continue;
|
|
388
|
+
active.push({ full, sessionId: f.replace(/\.json$/, ''), mtimeMs: st.mtimeMs });
|
|
389
|
+
}
|
|
390
|
+
active.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
391
|
+
const entries = [];
|
|
392
|
+
for (const a of active.slice(0, 3)) {
|
|
393
|
+
// cwd 包含匹配(原文本容错:原分隔符 / 正斜杠 / JSON 转义双反斜杠三形态命中其一即可)
|
|
394
|
+
let text = '';
|
|
395
|
+
try { text = readFileSync(a.full, 'utf8'); } catch { continue; }
|
|
396
|
+
const cwds = cwdCandidates.filter(Boolean);
|
|
397
|
+
const hit = cwds.some(c => text.includes(c) || text.includes(toPosix(c))
|
|
398
|
+
|| text.includes(JSON.stringify(c).slice(1, -1)));
|
|
399
|
+
if (!hit) continue;
|
|
400
|
+
entries.push({
|
|
401
|
+
harness: 'opencode', format: 'opencode-session-json-tree', detected_via: 'opencode-session-info-cwd',
|
|
402
|
+
// 会话内容分散在 info/message/part 三棵子树,log_path 指向 session/ 根(平台侧按 format 遍历)
|
|
403
|
+
log_path: toPosix(sessionRoot), agent_cwd: cwdPosix,
|
|
404
|
+
session_id: a.sessionId, originator: null,
|
|
405
|
+
mtime_ms: a.mtimeMs,
|
|
406
|
+
});
|
|
407
|
+
break; // 同根目录只登一条(log_path 相同),session_id 取最新命中会话
|
|
408
|
+
}
|
|
409
|
+
return entries;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* 探测器注册表。tier 语义:
|
|
414
|
+
* - precise:cwd/env 精确归属(claude-code / codex / zcode / pi / deepseek-dsh),恒参与;
|
|
415
|
+
* - loose:无归属线索(cursor / opencode),**仅在 precise 全落空时启用**——
|
|
416
|
+
* 防「Cursor IDE 在别的项目聊天 / 其他项目 opencode 会话活跃」被误报进当前登记。
|
|
417
|
+
* 新增 CLI = 加一个探测器对象 + protocol 文档补布局与格式说明。
|
|
418
|
+
*/
|
|
419
|
+
const HARNESS_DETECTORS = [
|
|
420
|
+
{ name: 'claude-code', tier: 'precise', detect: detectClaudeCode },
|
|
421
|
+
{ name: 'codex', tier: 'precise', detect: detectCodex },
|
|
422
|
+
{ name: 'zcode', tier: 'precise', detect: detectZcode },
|
|
423
|
+
{ name: 'pi', tier: 'precise', detect: detectPi },
|
|
424
|
+
{ name: 'deepseek-dsh', tier: 'precise', detect: detectDsh },
|
|
425
|
+
{ name: 'cursor', tier: 'loose', detect: detectCursor },
|
|
426
|
+
{ name: 'opencode', tier: 'loose', detect: detectOpencode },
|
|
427
|
+
];
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* 探测当前驱动 CLI 的本地 agent 会话日志(纯读,不写盘)。
|
|
431
|
+
*
|
|
432
|
+
* @param {object} [opts]
|
|
433
|
+
* @param {string[]} [opts.cwdCandidates] - agent 可能的 cwd 列表(CLI 原始 cwd + 纠正后项目根;
|
|
434
|
+
* agent 在子目录启动时部分 harness 日志挂在子目录维度,两者都探)
|
|
435
|
+
* @param {object} [opts.env] - 默认 process.env(可注入测试)
|
|
436
|
+
* @param {string} [opts.homeDir] - 默认 homedir()(可注入测试)
|
|
437
|
+
* @param {number} [opts.now] - 默认 Date.now()(可注入测试)
|
|
438
|
+
* @param {number} [opts.windowMs] - 活跃窗口,默认 15min
|
|
439
|
+
* @returns {Array<{harness:string, log_path:string, format:string, detected_via:string,
|
|
440
|
+
* agent_cwd:string, session_id:string|null, originator:string|null, mtime_ms:number|null}>}
|
|
441
|
+
* 探测结果(mtime_ms 新→旧);空数组 = 未探测到(不在 agent 环境或无活跃会话)
|
|
442
|
+
*/
|
|
443
|
+
export function detectAgentLogEntries({
|
|
444
|
+
cwdCandidates = [],
|
|
445
|
+
env = process.env,
|
|
446
|
+
homeDir = homedir(),
|
|
447
|
+
now = Date.now(),
|
|
448
|
+
windowMs = ACTIVE_WINDOW_MS,
|
|
449
|
+
} = {}) {
|
|
450
|
+
const entries = [];
|
|
451
|
+
|
|
452
|
+
// ── 优先级 1:env 显式覆盖(绝对路径;相对路径忽略——产物里的路径必须可直接消费)──
|
|
453
|
+
// 不依赖任何 harness 标记:daemon/用户最可靠的指定通道(其他 CLI 一律走这条路)
|
|
454
|
+
const override = env[AGENT_LOG_ENV_OVERRIDE];
|
|
455
|
+
if (typeof override === 'string' && override.trim()) {
|
|
456
|
+
const p = override.trim();
|
|
457
|
+
if (isAbsolute(p)) {
|
|
458
|
+
entries.push({
|
|
459
|
+
harness: 'env-override', format: p.endsWith('.jsonl') ? 'jsonl' : 'unknown',
|
|
460
|
+
detected_via: 'env',
|
|
461
|
+
log_path: toPosix(p), agent_cwd: toPosix(cwdCandidates[0] || ''),
|
|
462
|
+
session_id: null, originator: null,
|
|
463
|
+
mtime_ms: statSafe(p)?.mtimeMs ?? null,
|
|
464
|
+
});
|
|
465
|
+
} else {
|
|
466
|
+
debugLog(`${AGENT_LOG_ENV_OVERRIDE} 非绝对路径,忽略: ${p}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// ── 优先级 2:已登记 harness 自动探测(precise 恒参与;loose 仅在 precise 全落空时启用)──
|
|
471
|
+
const ctx = { cwdCandidates, env, homeDir, now, windowMs };
|
|
472
|
+
let preciseFound = false;
|
|
473
|
+
for (const h of HARNESS_DETECTORS) {
|
|
474
|
+
if (h.tier === 'loose' && preciseFound) continue;
|
|
475
|
+
try {
|
|
476
|
+
const found = h.detect(ctx);
|
|
477
|
+
if (found.length > 0) {
|
|
478
|
+
entries.push(...found);
|
|
479
|
+
if (h.tier === 'precise') preciseFound = true;
|
|
480
|
+
}
|
|
481
|
+
} catch (e) {
|
|
482
|
+
debugLog(`${h.name} 探测异常(跳过): ${e && e.message ? e.message : e}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
entries.sort((a, b) => (b.mtime_ms ?? 0) - (a.mtime_ms ?? 0));
|
|
487
|
+
return entries.slice(0, 12);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* 读 agent 日志上报配置(与进度同步 quicklog 推送同风格,自带轻量 local.yaml 解析,
|
|
492
|
+
* 不 import sync.js 防拖 ProgressManager 重链)。
|
|
493
|
+
*
|
|
494
|
+
* 优先级:env SILLYHUB_PLATFORM_URL + SILLYHUB_PLATFORM_TOKEN(两键齐全才生效——
|
|
495
|
+
* daemon 注入通道,平台模式 specRoot 的 local.yaml 无 platform 段时靠它)>
|
|
496
|
+
* specBase/local.yaml platform 段(url + token,与链路 A 同源)。
|
|
497
|
+
* @returns {{url:string, token:string}|null} 未配置返回 null(合法状态,静默跳过上报)
|
|
498
|
+
*/
|
|
499
|
+
function readPlatformPushConfig(specBase, env = process.env) {
|
|
500
|
+
if (env.SILLYHUB_PLATFORM_URL && env.SILLYHUB_PLATFORM_TOKEN) {
|
|
501
|
+
return { url: env.SILLYHUB_PLATFORM_URL, token: env.SILLYHUB_PLATFORM_TOKEN };
|
|
502
|
+
}
|
|
503
|
+
try {
|
|
504
|
+
const yamlPath = join(specBase, 'local.yaml');
|
|
505
|
+
if (!existsSync(yamlPath)) return null;
|
|
506
|
+
const lines = readFileSync(yamlPath, 'utf8').split(/\r?\n/);
|
|
507
|
+
let inPlatform = false;
|
|
508
|
+
const cfg = {};
|
|
509
|
+
for (const line of lines) {
|
|
510
|
+
if (!line.trim() || line.trim().startsWith('#')) continue;
|
|
511
|
+
if (!/^\s/.test(line)) inPlatform = line.startsWith('platform:');
|
|
512
|
+
else if (inPlatform) {
|
|
513
|
+
const m = line.match(/^\s+(url|token)\s*:\s*(.*)$/);
|
|
514
|
+
if (m) {
|
|
515
|
+
let v = m[2].trim();
|
|
516
|
+
if (v.length >= 2 && ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'")))) v = v.slice(1, -1);
|
|
517
|
+
if (v) cfg[m[1]] = v;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (!cfg.url || !cfg.token) return null;
|
|
522
|
+
return cfg;
|
|
523
|
+
} catch {
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* 上报 agent 日志登记到平台:POST {url}/api/agent-logs(Bearer platform token)。
|
|
530
|
+
* 平台端落库后在会话视图展示;内容解析由平台/daemon 按路径读本地文件完成。
|
|
531
|
+
* best-effort:无配置静默跳过;网络/非 2xx/超时 warn 一行不抛(本地产物已落盘兜底)。
|
|
532
|
+
* @param {object} p
|
|
533
|
+
* @param {string} p.specBase - local.yaml 所在 spec 基路径
|
|
534
|
+
* @param {object} [p.env] - env(默认 process.env;测试注入用)
|
|
535
|
+
* @param {object} p.payload - 上报 body(见 protocol 文档 §3)
|
|
536
|
+
* @returns {Promise<{pushed:boolean|null, reason?:string}>} pushed=null 表示未配置/被禁用
|
|
537
|
+
*/
|
|
538
|
+
async function pushAgentLogToPlatform({ specBase, env = process.env, payload }) {
|
|
539
|
+
if (env.SILLYSPEC_AGENT_LOG_PUSH === '0') {
|
|
540
|
+
debugLog('SILLYSPEC_AGENT_LOG_PUSH=0,上报关闭');
|
|
541
|
+
return { pushed: null, reason: 'disabled' };
|
|
542
|
+
}
|
|
543
|
+
const cfg = readPlatformPushConfig(specBase, env);
|
|
544
|
+
if (!cfg) {
|
|
545
|
+
debugLog('未配置 platform 段/env(未连接平台,合法状态),跳过上报');
|
|
546
|
+
return { pushed: null, reason: 'no-config' };
|
|
547
|
+
}
|
|
548
|
+
const controller = new AbortController();
|
|
549
|
+
const timer = setTimeout(() => controller.abort(), PUSH_TIMEOUT_MS);
|
|
550
|
+
try {
|
|
551
|
+
const res = await fetch(`${cfg.url.replace(/\/+$/, '')}/api/agent-logs`, {
|
|
552
|
+
method: 'POST',
|
|
553
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${cfg.token}` },
|
|
554
|
+
body: JSON.stringify(payload),
|
|
555
|
+
signal: controller.signal,
|
|
556
|
+
});
|
|
557
|
+
if (!res.ok) {
|
|
558
|
+
console.warn(`[agent-log-push] → HTTP ${res.status}(本地产物已落盘兜底,不影响主流程)`);
|
|
559
|
+
return { pushed: false, reason: `http-${res.status}` };
|
|
560
|
+
}
|
|
561
|
+
return { pushed: true };
|
|
562
|
+
} catch (err) {
|
|
563
|
+
const msg = err && err.name === 'AbortError' ? '超时' : (err && err.message ? err.message : err);
|
|
564
|
+
console.warn(`[agent-log-push] 上报失败: ${msg}(本地产物已落盘兜底)`);
|
|
565
|
+
return { pushed: false, reason: 'network' };
|
|
566
|
+
} finally {
|
|
567
|
+
clearTimeout(timer);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* 读 agent-session-log.json 产物(不存在/损坏返回 null)。
|
|
573
|
+
* @param {string} runtimeRoot - .runtime 根目录(resolveRuntimeRoot 的返回值)
|
|
574
|
+
* @returns {object|null}
|
|
575
|
+
*/
|
|
576
|
+
export function readAgentLogArtifact(runtimeRoot) {
|
|
577
|
+
const p = join(runtimeRoot, AGENT_LOG_ARTIFACT_FILENAME);
|
|
578
|
+
if (!existsSync(p)) return null;
|
|
579
|
+
try {
|
|
580
|
+
const parsed = JSON.parse(readFileSync(p, 'utf8'));
|
|
581
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
582
|
+
} catch {
|
|
583
|
+
debugLog(`产物损坏,按无产物处理: ${p}`);
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* 登记 agent 会话日志路径进 <runtimeRoot>/agent-session-log.json(run 命令入口调用)。
|
|
590
|
+
*
|
|
591
|
+
* 合并语义:按 log_path 去重——同路径 invocations+1 / 刷新 last_seen_at 与文件 stat;
|
|
592
|
+
* 新路径追加;entries 按 last_seen_at 新→旧排序(同毫秒平局按 seq 登记序决胜),
|
|
593
|
+
* 上限 10 条(超出淘汰最旧)。
|
|
594
|
+
* 探测不到(非 agent 环境)不写盘不上报。全程 best-effort:失败 warn 一行返回 null 不抛。
|
|
595
|
+
* 落盘后立即 REST 上报平台(POST /api/agent-logs,主通道),上报失败本地产物兜底。
|
|
596
|
+
*
|
|
597
|
+
* @param {object} p
|
|
598
|
+
* @param {string} p.cwd - CLI 当前 cwd(项目根;平台模式 = source root)
|
|
599
|
+
* @param {string} [p.invokedCwd] - CLI 原始 cwd(cwd 纠正前;agent 子目录启动时探测用)
|
|
600
|
+
* @param {object} [p.platformOpts] - 平台参数(workspaceId/scanRunId 进产物元信息)
|
|
601
|
+
* @param {string} p.specBase - 规范基路径(本地模式 cwd/.sillyspec;平台模式 specRoot)
|
|
602
|
+
* @param {string} [p.command] - 触发登记的命令描述(flag 名级,不含 flag 值)
|
|
603
|
+
* @param {object} [p.context] - 会话化上下文(2026-08-23-agent-activity-sessions,可选):
|
|
604
|
+
* {hubSessionId, changeKey, quickId}——hubSessionId 为 body 级(run 所属平台会话唯一,
|
|
605
|
+
* daemon 注入 env SILLYHUB_SESSION_ID 的兜底也在此读取);changeKey/quickId 为 entry 级
|
|
606
|
+
* (本次 run 检出/更新的 entry 随 run 持久化,quick 会话 quickId 优先互斥)。缺省全不带。
|
|
607
|
+
* @param {object} [p.env] - 探测用 env(默认 process.env;测试注入用)
|
|
608
|
+
* @param {string} [p.homeDir] - 探测用 home(默认 homedir();测试注入用)
|
|
609
|
+
* @param {number} [p.now] - 探测用时钟(默认 Date.now();测试注入用)
|
|
610
|
+
* @param {number} [p.windowMs] - 活跃窗口(默认 15min)
|
|
611
|
+
* @returns {Promise<{artifactPath:string, detected:number, latestLogPath:string|null, isNew:boolean, pushed:boolean|null}|null>}
|
|
612
|
+
* null = 未探测到 / 失败(调用方无需区分)
|
|
613
|
+
*/
|
|
614
|
+
export async function recordAgentLogInvocation({ cwd, invokedCwd, platformOpts = {}, specBase, command, context = {}, env, homeDir, now, windowMs } = {}) {
|
|
615
|
+
try {
|
|
616
|
+
if (!cwd || !specBase) return null;
|
|
617
|
+
const cwdCandidates = [...new Set([invokedCwd, cwd].filter(Boolean))];
|
|
618
|
+
const detected = detectAgentLogEntries({ cwdCandidates, env, homeDir, now, windowMs });
|
|
619
|
+
if (detected.length === 0) return null;
|
|
620
|
+
|
|
621
|
+
// 会话化上下文(best-effort 消毒,解析失败不带):entry 级 ctx(changeKey/quickId)随
|
|
622
|
+
// 检出/更新该 entry 的本次 run 持久化;hubSessionId 为 body 级——context 显式传入优先,
|
|
623
|
+
// 缺省回落 env SILLYHUB_SESSION_ID(daemon 派发平台会话时的注入通道,见 protocol 文档 §1)。
|
|
624
|
+
const hubSessionId = pickCtxId(context.hubSessionId) || pickCtxId((env || process.env).SILLYHUB_SESSION_ID);
|
|
625
|
+
const ctxChangeKey = pickCtxId(context.changeKey);
|
|
626
|
+
const ctxQuickId = pickCtxId(context.quickId);
|
|
627
|
+
|
|
628
|
+
const runtimeRoot = resolveRuntimeRoot(platformOpts, specBase);
|
|
629
|
+
const artifactPath = join(runtimeRoot, AGENT_LOG_ARTIFACT_FILENAME);
|
|
630
|
+
const nowIso = new Date().toISOString();
|
|
631
|
+
|
|
632
|
+
// 读-改-写按文件锁串行化(BUG-17 同款口径:多会话并发 run 时防互相覆盖丢条目)
|
|
633
|
+
const merged = await withFileLock(`${artifactPath}.lock`, () => {
|
|
634
|
+
const existing = readAgentLogArtifact(runtimeRoot);
|
|
635
|
+
const byPath = new Map((existing?.entries || []).map(e => [e.log_path, e]));
|
|
636
|
+
|
|
637
|
+
// 平局决胜序号:last_seen_at 是毫秒精度 ISO,快机器上连续登记落在同一毫秒打平,
|
|
638
|
+
// 稳定排序会让旧条目留在首位(latestLogPath 假"未更新")。seq 按登记触及顺序单调
|
|
639
|
+
// 递增并随条目持久化,平局时后触及者胜(存量产物无 seq 视为 0,老条目间次序不变)。
|
|
640
|
+
let seq = (existing?.entries || []).reduce((m, e) => Math.max(m, Number(e.seq) || 0), 0);
|
|
641
|
+
|
|
642
|
+
for (const det of detected) {
|
|
643
|
+
const st = statSafe(det.log_path);
|
|
644
|
+
const prev = byPath.get(det.log_path);
|
|
645
|
+
byPath.set(det.log_path, {
|
|
646
|
+
...(prev || {}),
|
|
647
|
+
harness: det.harness,
|
|
648
|
+
log_path: det.log_path,
|
|
649
|
+
format: det.format,
|
|
650
|
+
detected_via: det.detected_via,
|
|
651
|
+
agent_cwd: det.agent_cwd,
|
|
652
|
+
session_id: det.session_id ?? prev?.session_id ?? null,
|
|
653
|
+
originator: det.originator ?? prev?.originator ?? null,
|
|
654
|
+
// entry 级 ctx(D-009):本次 run 非空者写入;context 未带(如无 --change 的 run)
|
|
655
|
+
// 保留原值——未被本次 run 触及的存量 entry 不进此循环,展开即保留原 ctx 不追新
|
|
656
|
+
change_key: ctxChangeKey ?? prev?.change_key ?? null,
|
|
657
|
+
quick_id: ctxQuickId ?? prev?.quick_id ?? null,
|
|
658
|
+
exists: Boolean(st),
|
|
659
|
+
size_bytes: st ? st.size : null,
|
|
660
|
+
mtime_ms: st ? st.mtimeMs : (det.mtime_ms ?? null),
|
|
661
|
+
first_seen_at: prev?.first_seen_at || nowIso,
|
|
662
|
+
last_seen_at: nowIso,
|
|
663
|
+
invocations: (prev?.invocations || 0) + 1,
|
|
664
|
+
last_command: command || null,
|
|
665
|
+
seq: ++seq,
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const entries = [...byPath.values()]
|
|
670
|
+
.sort((a, b) => (b.last_seen_at || '').localeCompare(a.last_seen_at || '') || ((b.seq || 0) - (a.seq || 0)))
|
|
671
|
+
.slice(0, MAX_ENTRIES);
|
|
672
|
+
|
|
673
|
+
const artifact = {
|
|
674
|
+
schema_version: AGENT_LOG_SCHEMA_VERSION,
|
|
675
|
+
generated_at: nowIso,
|
|
676
|
+
agent_cwd: toPosix(invokedCwd || cwd),
|
|
677
|
+
workspace_id: platformOpts.workspaceId ?? null,
|
|
678
|
+
scan_run_id: platformOpts.scanRunId ?? null,
|
|
679
|
+
entries,
|
|
680
|
+
};
|
|
681
|
+
mkdirSync(runtimeRoot, { recursive: true });
|
|
682
|
+
writeAtomicSync(artifactPath, JSON.stringify(artifact, null, 2) + '\n');
|
|
683
|
+
return { entries, prevLatest: existing?.entries?.[0]?.log_path || null };
|
|
684
|
+
}, { timeoutMs: MERGE_LOCK_TIMEOUT_MS });
|
|
685
|
+
|
|
686
|
+
const latest = merged.entries[0] || null;
|
|
687
|
+
const isNew = Boolean(latest && latest.log_path !== merged.prevLatest);
|
|
688
|
+
|
|
689
|
+
// 上报平台(主通道,与进度上报同风格;失败本地产物兜底)。不受平台模式 sentinel
|
|
690
|
+
// 限制——进度同步在平台模式跳过是因为 daemon 有自有进度链路,agent 日志没有。
|
|
691
|
+
const push = await pushAgentLogToPlatform({
|
|
692
|
+
specBase,
|
|
693
|
+
env: env || process.env,
|
|
694
|
+
payload: {
|
|
695
|
+
schema_version: AGENT_LOG_SCHEMA_VERSION,
|
|
696
|
+
pushed_at: nowIso,
|
|
697
|
+
agent_cwd: toPosix(invokedCwd || cwd),
|
|
698
|
+
workspace_id: platformOpts.workspaceId ?? null,
|
|
699
|
+
scan_run_id: platformOpts.scanRunId ?? null,
|
|
700
|
+
// body 级会话关联键(run 所属平台会话唯一;非空才带,schema_version 保持 1 纯可选增量)
|
|
701
|
+
...(hubSessionId ? { hub_session_id: hubSessionId } : {}),
|
|
702
|
+
// entries 各自带 entry 级 ctx(change_key/quick_id,随 entry 持久化的原值或本次 run 的新值)
|
|
703
|
+
entries: merged.entries,
|
|
704
|
+
},
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
return {
|
|
708
|
+
artifactPath: toPosix(artifactPath),
|
|
709
|
+
detected: detected.length,
|
|
710
|
+
latestLogPath: latest ? latest.log_path : null,
|
|
711
|
+
isNew,
|
|
712
|
+
pushed: push.pushed,
|
|
713
|
+
};
|
|
714
|
+
} catch (e) {
|
|
715
|
+
console.warn(`[agent-log] 登记失败(忽略,不影响主流程): ${e && e.message ? e.message : e}`);
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* 解析 agent-session-log.json 产物路径的落点(查询命令用;与写入侧同口径)。
|
|
722
|
+
*
|
|
723
|
+
* 优先级:平台指针(cwd/.sillyspec-platform.json 的 runtimeRoot/specRoot)> 显式 specDir
|
|
724
|
+
* > 本地 cwd/.sillyspec。runtimeRoot 缺省回落 <specRoot>/.runtime(对齐 resolveRuntimeRoot)。
|
|
725
|
+
* @returns {{artifactPath: string, runtimeRoot: string, restored: boolean}}
|
|
726
|
+
*/
|
|
727
|
+
export function resolveAgentLogArtifactPath({ cwd, specDir } = {}) {
|
|
728
|
+
let runtimeRoot = null;
|
|
729
|
+
let restored = false;
|
|
730
|
+
if (cwd) {
|
|
731
|
+
try {
|
|
732
|
+
const pointerPath = join(cwd, '.sillyspec-platform.json');
|
|
733
|
+
if (existsSync(pointerPath)) {
|
|
734
|
+
const saved = JSON.parse(readFileSync(pointerPath, 'utf8'));
|
|
735
|
+
if (saved.runtimeRoot) {
|
|
736
|
+
runtimeRoot = saved.runtimeRoot;
|
|
737
|
+
restored = true;
|
|
738
|
+
} else if (saved.specRoot) {
|
|
739
|
+
runtimeRoot = join(saved.specRoot, '.runtime');
|
|
740
|
+
restored = true;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
} catch { /* 指针损坏按本地模式处理 */ }
|
|
744
|
+
}
|
|
745
|
+
if (!runtimeRoot) {
|
|
746
|
+
const specBase = specDir || join(cwd || process.cwd(), '.sillyspec');
|
|
747
|
+
runtimeRoot = join(specBase, '.runtime');
|
|
748
|
+
}
|
|
749
|
+
return { artifactPath: join(runtimeRoot, AGENT_LOG_ARTIFACT_FILENAME), runtimeRoot, restored };
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* `sillyspec agent-log` 查询命令实现(index.js case 'agent-log' 薄封装)。
|
|
754
|
+
*
|
|
755
|
+
* 默认读产物展示;--detect 现场探测(不写盘)。--json 输出纯 JSON(stdout 无其他内容)。
|
|
756
|
+
* @param {string[]} subArgs - 'agent-log' 之后的参数
|
|
757
|
+
* @param {object} opts - { json, cwd, specDir }
|
|
758
|
+
*/
|
|
759
|
+
export async function cmdAgentLog(subArgs, { json = false, cwd = process.cwd(), specDir = null } = {}) {
|
|
760
|
+
const detectOnly = subArgs.includes('--detect');
|
|
761
|
+
if (detectOnly) {
|
|
762
|
+
const detected = detectAgentLogEntries({ cwdCandidates: [cwd] });
|
|
763
|
+
if (json) {
|
|
764
|
+
console.log(JSON.stringify({ detected }, null, 2));
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (detected.length === 0) {
|
|
768
|
+
console.log('📭 未探测到本地 agent 会话日志。');
|
|
769
|
+
console.log(' 支持: Claude Code / Codex / ZCode 自动探测;其他 CLI 用 SILLYSPEC_AGENT_LOG=<日志绝对路径> 显式指定。');
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
console.log(`🔍 现场探测到 ${detected.length} 条 agent 日志:`);
|
|
773
|
+
for (const d of detected) {
|
|
774
|
+
console.log(` 📄 ${d.log_path}`);
|
|
775
|
+
console.log(` ${d.harness} / ${d.format} / via=${d.detected_via}${d.session_id ? ` / session=${d.session_id}` : ''}${d.originator ? ` / originator=${d.originator}` : ''}`);
|
|
776
|
+
}
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const { artifactPath, runtimeRoot, restored } = resolveAgentLogArtifactPath({ cwd, specDir });
|
|
781
|
+
const artifact = readAgentLogArtifact(runtimeRoot);
|
|
782
|
+
if (json) {
|
|
783
|
+
console.log(JSON.stringify({ artifact_path: toPosix(artifactPath), platform_restored: restored, artifact }, null, 2));
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (!artifact || !Array.isArray(artifact.entries) || artifact.entries.length === 0) {
|
|
787
|
+
console.log(`📭 无 agent 日志登记产物(${toPosix(artifactPath)})。`);
|
|
788
|
+
console.log(' run <stage> 命令在 agent 环境内执行时自动登记;或用 agent-log --detect 现场探测。');
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
console.log(`📄 本地 agent 会话日志登记(${toPosix(artifactPath)},${artifact.entries.length} 条)`);
|
|
792
|
+
if (artifact.workspace_id || artifact.scan_run_id) {
|
|
793
|
+
console.log(` workspace=${artifact.workspace_id || '-'} scan_run=${artifact.scan_run_id || '-'}`);
|
|
794
|
+
}
|
|
795
|
+
for (const e of artifact.entries) {
|
|
796
|
+
const state = e.exists ? '✅' : '⚠️(文件已不存在)';
|
|
797
|
+
console.log(` ${state} ${e.log_path}`);
|
|
798
|
+
const meta = [e.harness, e.format, e.session_id ? `session=${e.session_id}` : null, e.originator ? `originator=${e.originator}` : null]
|
|
799
|
+
.filter(Boolean).join(' / ');
|
|
800
|
+
console.log(` ${meta} / 见过 ${e.invocations} 次 / 最近 ${e.last_seen_at}${e.last_command ? ` / ${e.last_command}` : ''}`);
|
|
801
|
+
}
|
|
802
|
+
console.log(' 协议: docs/platform-agent-log-protocol.md(run 命令自动 REST 上报平台 POST /api/agent-logs;此产物为本地留底)');
|
|
803
|
+
}
|