timsquad 2.0.0 → 3.3.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/README.md +168 -234
- package/dist/commands/daemon.d.ts +7 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +140 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/feedback.d.ts +9 -0
- package/dist/commands/feedback.d.ts.map +1 -1
- package/dist/commands/feedback.js +251 -11
- package/dist/commands/feedback.js.map +1 -1
- package/dist/commands/full.js +2 -2
- package/dist/commands/full.js.map +1 -1
- package/dist/commands/git/commit.d.ts.map +1 -1
- package/dist/commands/git/commit.js +1 -4
- package/dist/commands/git/commit.js.map +1 -1
- package/dist/commands/improve.d.ts +3 -0
- package/dist/commands/improve.d.ts.map +1 -0
- package/dist/commands/improve.js +286 -0
- package/dist/commands/improve.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +110 -22
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/knowledge.d.ts +3 -0
- package/dist/commands/knowledge.d.ts.map +1 -0
- package/dist/commands/knowledge.js +316 -0
- package/dist/commands/knowledge.js.map +1 -0
- package/dist/commands/log.d.ts +27 -0
- package/dist/commands/log.d.ts.map +1 -1
- package/dist/commands/log.js +1167 -2
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/meta-index.d.ts +3 -0
- package/dist/commands/meta-index.d.ts.map +1 -0
- package/dist/commands/meta-index.js +401 -0
- package/dist/commands/meta-index.js.map +1 -0
- package/dist/commands/metrics.d.ts.map +1 -1
- package/dist/commands/metrics.js +640 -100
- package/dist/commands/metrics.js.map +1 -1
- package/dist/commands/retro.d.ts.map +1 -1
- package/dist/commands/retro.js +606 -58
- package/dist/commands/retro.js.map +1 -1
- package/dist/commands/session.d.ts +3 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +346 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/upgrade.d.ts +8 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +287 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/workflow.d.ts +3 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/commands/workflow.js +607 -0
- package/dist/commands/workflow.js.map +1 -0
- package/dist/daemon/context-writer.d.ts +16 -0
- package/dist/daemon/context-writer.d.ts.map +1 -0
- package/dist/daemon/context-writer.js +35 -0
- package/dist/daemon/context-writer.js.map +1 -0
- package/dist/daemon/entry.d.ts +7 -0
- package/dist/daemon/entry.d.ts.map +1 -0
- package/dist/daemon/entry.js +17 -0
- package/dist/daemon/entry.js.map +1 -0
- package/dist/daemon/event-queue.d.ts +52 -0
- package/dist/daemon/event-queue.d.ts.map +1 -0
- package/dist/daemon/event-queue.js +255 -0
- package/dist/daemon/event-queue.js.map +1 -0
- package/dist/daemon/file-watcher.d.ts +19 -0
- package/dist/daemon/file-watcher.d.ts.map +1 -0
- package/dist/daemon/file-watcher.js +87 -0
- package/dist/daemon/file-watcher.js.map +1 -0
- package/dist/daemon/index.d.ts +28 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +204 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/jsonl-watcher.d.ts +49 -0
- package/dist/daemon/jsonl-watcher.d.ts.map +1 -0
- package/dist/daemon/jsonl-watcher.js +258 -0
- package/dist/daemon/jsonl-watcher.js.map +1 -0
- package/dist/daemon/meta-cache.d.ts +62 -0
- package/dist/daemon/meta-cache.d.ts.map +1 -0
- package/dist/daemon/meta-cache.js +240 -0
- package/dist/daemon/meta-cache.js.map +1 -0
- package/dist/daemon/shutdown.d.ts +21 -0
- package/dist/daemon/shutdown.d.ts.map +1 -0
- package/dist/daemon/shutdown.js +158 -0
- package/dist/daemon/shutdown.js.map +1 -0
- package/dist/index.js +24 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-composer.d.ts +38 -0
- package/dist/lib/agent-composer.d.ts.map +1 -0
- package/dist/lib/agent-composer.js +128 -0
- package/dist/lib/agent-composer.js.map +1 -0
- package/dist/lib/agent-generator.d.ts +22 -0
- package/dist/lib/agent-generator.d.ts.map +1 -0
- package/dist/lib/agent-generator.js +150 -0
- package/dist/lib/agent-generator.js.map +1 -0
- package/dist/lib/ast-parser.d.ts +11 -0
- package/dist/lib/ast-parser.d.ts.map +1 -0
- package/dist/lib/ast-parser.js +282 -0
- package/dist/lib/ast-parser.js.map +1 -0
- package/dist/lib/config.d.ts +6 -2
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +27 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/meta-index.d.ts +19 -0
- package/dist/lib/meta-index.d.ts.map +1 -0
- package/dist/lib/meta-index.js +573 -0
- package/dist/lib/meta-index.js.map +1 -0
- package/dist/lib/project.js +1 -1
- package/dist/lib/project.js.map +1 -1
- package/dist/lib/skill-generator.d.ts +32 -0
- package/dist/lib/skill-generator.d.ts.map +1 -0
- package/dist/lib/skill-generator.js +187 -0
- package/dist/lib/skill-generator.js.map +1 -0
- package/dist/lib/template.d.ts +16 -2
- package/dist/lib/template.d.ts.map +1 -1
- package/dist/lib/template.js +175 -21
- package/dist/lib/template.js.map +1 -1
- package/dist/lib/ui-index.d.ts +12 -0
- package/dist/lib/ui-index.d.ts.map +1 -0
- package/dist/lib/ui-index.js +239 -0
- package/dist/lib/ui-index.js.map +1 -0
- package/dist/lib/ui-parser.d.ts +12 -0
- package/dist/lib/ui-parser.d.ts.map +1 -0
- package/dist/lib/ui-parser.js +472 -0
- package/dist/lib/ui-parser.js.map +1 -0
- package/dist/lib/update-check.d.ts +6 -0
- package/dist/lib/update-check.d.ts.map +1 -0
- package/dist/lib/update-check.js +121 -0
- package/dist/lib/update-check.js.map +1 -0
- package/dist/lib/upgrade-backup.d.ts +33 -0
- package/dist/lib/upgrade-backup.d.ts.map +1 -0
- package/dist/lib/upgrade-backup.js +101 -0
- package/dist/lib/upgrade-backup.js.map +1 -0
- package/dist/lib/version.d.ts +19 -0
- package/dist/lib/version.d.ts.map +1 -0
- package/dist/lib/version.js +35 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/lib/workflow-state.d.ts +48 -0
- package/dist/lib/workflow-state.d.ts.map +1 -0
- package/dist/lib/workflow-state.js +67 -0
- package/dist/lib/workflow-state.js.map +1 -0
- package/dist/types/config.d.ts +102 -2
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +173 -9
- package/dist/types/config.js.map +1 -1
- package/dist/types/feedback.d.ts +59 -1
- package/dist/types/feedback.d.ts.map +1 -1
- package/dist/types/feedback.js +1 -4
- package/dist/types/feedback.js.map +1 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/meta-index.d.ts +146 -0
- package/dist/types/meta-index.d.ts.map +1 -0
- package/dist/types/meta-index.js +7 -0
- package/dist/types/meta-index.js.map +1 -0
- package/dist/types/project.d.ts +19 -3
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +24 -1
- package/dist/types/project.js.map +1 -1
- package/dist/types/task-log.d.ts +208 -0
- package/dist/types/task-log.d.ts.map +1 -0
- package/dist/types/task-log.js +6 -0
- package/dist/types/task-log.js.map +1 -0
- package/dist/types/ui-meta.d.ts +118 -0
- package/dist/types/ui-meta.d.ts.map +1 -0
- package/dist/types/ui-meta.js +7 -0
- package/dist/types/ui-meta.js.map +1 -0
- package/package.json +12 -4
- package/templates/base/agents/base/tsq-architect.md +68 -0
- package/templates/base/agents/base/tsq-dba.md +56 -0
- package/templates/base/agents/base/tsq-designer.md +72 -0
- package/templates/base/agents/base/tsq-developer.md +67 -0
- package/templates/base/agents/base/tsq-qa.md +55 -0
- package/templates/base/agents/base/tsq-security.md +65 -0
- package/templates/base/agents/overlays/domain/general-web/_common.md +11 -0
- package/templates/base/agents/overlays/platform/claude-code.md +12 -0
- package/templates/base/config.template.yaml +213 -0
- package/templates/base/knowledge/checklists/accessibility.md +37 -0
- package/templates/base/knowledge/checklists/architecture-review.md +28 -0
- package/templates/base/knowledge/checklists/database-standards.md +84 -0
- package/templates/base/knowledge/checklists/design-reference.md +97 -0
- package/templates/base/knowledge/checklists/security.md +50 -0
- package/templates/base/knowledge/checklists/ssot-validation.md +19 -0
- package/templates/base/knowledge/domains/_template.md +16 -0
- package/templates/base/knowledge/platforms/_template.md +16 -0
- package/templates/base/knowledge/templates/sequence-report.md +44 -0
- package/templates/base/knowledge/templates/task-result.md +105 -0
- package/templates/base/skills/_template/SKILL.md +59 -0
- package/templates/base/skills/_template/references/_template.md +35 -0
- package/templates/base/skills/_template/rules/_sections.md +34 -0
- package/templates/base/skills/_template/rules/_template.md +32 -0
- package/templates/base/skills/_template/scripts/_template.sh +31 -0
- package/templates/base/skills/architecture/SKILL.md +54 -0
- package/templates/base/skills/architecture/references/adr-template.md +50 -0
- package/templates/base/skills/architecture/references/api-design.md +64 -0
- package/templates/base/skills/backend/node/SKILL.md +81 -0
- package/templates/base/skills/backend/node/rules/async-patterns.md +81 -0
- package/templates/base/skills/backend/node/rules/deployment.md +33 -0
- package/templates/base/skills/backend/node/rules/env-config.md +41 -0
- package/templates/base/skills/backend/node/rules/error-handling.md +83 -0
- package/templates/base/skills/backend/node/rules/hono-app-setup.md +98 -0
- package/templates/base/skills/backend/node/rules/jwt-auth.md +76 -0
- package/templates/base/skills/backend/node/rules/middleware.md +56 -0
- package/templates/base/skills/backend/node/rules/testing.md +82 -0
- package/templates/base/skills/coding/SKILL.md +47 -0
- package/templates/base/skills/coding/rules/patterns.md +81 -0
- package/templates/base/skills/database/SKILL.md +98 -0
- package/templates/base/skills/database/prisma/SKILL.md +57 -0
- package/templates/base/skills/database/prisma/rules/queries.md +133 -0
- package/templates/base/skills/database/prisma/rules/schema-design.md +80 -0
- package/templates/base/skills/frontend/nextjs/SKILL.md +59 -0
- package/templates/base/skills/frontend/nextjs/rules/app-router.md +138 -0
- package/templates/base/skills/frontend/react/SKILL.md +86 -0
- package/templates/base/skills/frontend/react/rules/_sections.md +88 -0
- package/templates/base/skills/frontend/react/rules/anti-patterns.md +67 -0
- package/templates/base/skills/frontend/react/rules/async-api-routes.md +38 -0
- package/templates/base/skills/frontend/react/rules/async-defer-await.md +80 -0
- package/templates/base/skills/frontend/react/rules/async-dependencies.md +36 -0
- package/templates/base/skills/frontend/react/rules/async-parallel.md +28 -0
- package/templates/base/skills/frontend/react/rules/async-suspense-boundaries.md +99 -0
- package/templates/base/skills/frontend/react/rules/bundle-barrel-imports.md +59 -0
- package/templates/base/skills/frontend/react/rules/bundle-defer-third-party.md +49 -0
- package/templates/base/skills/frontend/react/rules/bundle-dynamic-imports.md +35 -0
- package/templates/base/skills/frontend/react/rules/component-conventions.md +74 -0
- package/templates/base/skills/frontend/react/rules/js-combine-iterations.md +32 -0
- package/templates/base/skills/frontend/react/rules/js-early-exit.md +50 -0
- package/templates/base/skills/frontend/react/rules/js-index-maps.md +37 -0
- package/templates/base/skills/frontend/react/rules/js-set-map-lookups.md +24 -0
- package/templates/base/skills/frontend/react/rules/rendering-conditional-render.md +40 -0
- package/templates/base/skills/frontend/react/rules/rendering-content-visibility.md +38 -0
- package/templates/base/skills/frontend/react/rules/rendering-hoist-jsx.md +46 -0
- package/templates/base/skills/frontend/react/rules/rerender-defer-reads.md +39 -0
- package/templates/base/skills/frontend/react/rules/rerender-derived-state.md +29 -0
- package/templates/base/skills/frontend/react/rules/rerender-memo.md +44 -0
- package/templates/base/skills/frontend/react/rules/rerender-transitions.md +40 -0
- package/templates/base/skills/frontend/react/rules/server-after-nonblocking.md +73 -0
- package/templates/base/skills/frontend/react/rules/server-cache-react.md +26 -0
- package/templates/base/skills/frontend/react/rules/server-parallel-fetching.md +79 -0
- package/templates/base/skills/frontend/react/rules/state-location.md +55 -0
- package/templates/base/skills/methodology/bdd/SKILL.md +69 -0
- package/templates/base/skills/methodology/bdd/rules/gherkin-patterns.md +113 -0
- package/templates/base/skills/methodology/ddd/SKILL.md +74 -0
- package/templates/base/skills/methodology/ddd/rules/strategic-patterns.md +98 -0
- package/templates/base/skills/methodology/debugging/SKILL.md +60 -0
- package/templates/base/skills/methodology/debugging/references/root-cause-tracing.md +84 -0
- package/templates/base/skills/methodology/tdd/SKILL.md +66 -0
- package/templates/base/skills/methodology/tdd/rules/real-world-example.md +88 -0
- package/templates/base/skills/methodology/tdd/rules/techniques.md +185 -0
- package/templates/base/skills/planning/SKILL.md +58 -0
- package/templates/base/skills/planning/references/prd-guide.md +47 -0
- package/templates/base/skills/planning/references/requirements-guide.md +46 -0
- package/templates/base/skills/prompt-engineering/SKILL.md +103 -0
- package/templates/base/skills/retrospective/SKILL.md +102 -0
- package/templates/base/skills/security/SKILL.md +55 -0
- package/templates/base/skills/security/rules/owasp-examples.md +119 -0
- package/templates/base/skills/security/scripts/check-secrets.sh +55 -0
- package/templates/base/skills/testing/SKILL.md +63 -0
- package/templates/base/skills/testing/references/testing-patterns.md +103 -0
- package/templates/base/skills/tsq-protocol/SKILL.md +51 -0
- package/templates/base/skills/typescript/SKILL.md +67 -0
- package/templates/base/skills/typescript/rules/type-patterns.md +135 -0
- package/templates/base/skills/typescript/rules/utility-types.md +76 -0
- package/templates/base/skills/ui-design/SKILL.md +70 -0
- package/templates/{common → base}/timsquad/feedback/routing-rules.yaml +1 -1
- package/templates/base/timsquad/process/phase-checklist.yaml +174 -0
- package/templates/{common → base}/timsquad/process/state-machine.xml +12 -0
- package/templates/{common → base}/timsquad/process/workflow-base.xml +124 -0
- package/templates/{common → base}/timsquad/retrospective/metrics/metrics-schema.json +46 -1
- package/templates/platforms/claude-code/CLAUDE.md.template +64 -0
- package/templates/platforms/claude-code/rules/adr-rules.md +32 -0
- package/templates/platforms/claude-code/rules/feedback-routing.md +18 -0
- package/templates/platforms/claude-code/rules/phase-management.md +23 -0
- package/templates/platforms/claude-code/rules/reporting-format.md +26 -0
- package/templates/platforms/claude-code/rules/sequence-management.md +72 -0
- package/templates/platforms/claude-code/rules/workspace-sync.md +33 -0
- package/templates/platforms/claude-code/settings.json +26 -0
- package/templates/project-types/api-backend/config.yaml +227 -0
- package/templates/project-types/api-backend/process/workflow.xml +214 -0
- package/templates/project-types/fintech/config.yaml +151 -0
- package/templates/project-types/fintech/process/workflow.xml +316 -0
- package/templates/project-types/infra/config.yaml +327 -0
- package/templates/project-types/infra/process/workflow.xml +296 -0
- package/templates/project-types/platform/config.yaml +254 -0
- package/templates/project-types/platform/process/workflow.xml +254 -0
- package/templates/project-types/web-app/config.yaml +198 -0
- package/templates/project-types/web-app/process/workflow.xml +210 -0
- package/templates/project-types/web-service/config.yaml +136 -0
- package/templates/project-types/web-service/process/workflow.xml +184 -0
- package/templates/common/CLAUDE.md.template +0 -254
- package/templates/common/claude/agents/tsq-dba.md +0 -290
- package/templates/common/claude/agents/tsq-designer.md +0 -304
- package/templates/common/claude/agents/tsq-developer.md +0 -118
- package/templates/common/claude/agents/tsq-planner.md +0 -90
- package/templates/common/claude/agents/tsq-prompter.md +0 -336
- package/templates/common/claude/agents/tsq-qa.md +0 -134
- package/templates/common/claude/agents/tsq-retro.md +0 -168
- package/templates/common/claude/agents/tsq-security.md +0 -190
- package/templates/common/claude/skills/architecture/SKILL.md +0 -123
- package/templates/common/claude/skills/backend/node/SKILL.md +0 -1015
- package/templates/common/claude/skills/coding/SKILL.md +0 -171
- package/templates/common/claude/skills/database/prisma/SKILL.md +0 -357
- package/templates/common/claude/skills/frontend/nextjs/SKILL.md +0 -279
- package/templates/common/claude/skills/frontend/react/SKILL.md +0 -1729
- package/templates/common/claude/skills/methodology/bdd/SKILL.md +0 -234
- package/templates/common/claude/skills/methodology/ddd/SKILL.md +0 -311
- package/templates/common/claude/skills/methodology/tdd/SKILL.md +0 -512
- package/templates/common/claude/skills/planning/SKILL.md +0 -90
- package/templates/common/claude/skills/security/SKILL.md +0 -234
- package/templates/common/claude/skills/testing/SKILL.md +0 -146
- package/templates/common/claude/skills/typescript/SKILL.md +0 -435
- package/templates/common/config.template.yaml +0 -131
- /package/templates/{common → base}/timsquad/architectures/clean/ARCHITECTURE.md +0 -0
- /package/templates/{common → base}/timsquad/architectures/clean/backend.xml +0 -0
- /package/templates/{common → base}/timsquad/architectures/clean/frontend.xml +0 -0
- /package/templates/{common → base}/timsquad/architectures/fsd/ARCHITECTURE.md +0 -0
- /package/templates/{common → base}/timsquad/architectures/fsd/frontend.xml +0 -0
- /package/templates/{common → base}/timsquad/architectures/hexagonal/ARCHITECTURE.md +0 -0
- /package/templates/{common → base}/timsquad/architectures/hexagonal/backend.xml +0 -0
- /package/templates/{common → base}/timsquad/constraints/competency-framework.xml +0 -0
- /package/templates/{common → base}/timsquad/constraints/ssot-schema.xml +0 -0
- /package/templates/{common → base}/timsquad/feedback/feedback-router.sh +0 -0
- /package/templates/{common → base}/timsquad/generators/data-design.xml +0 -0
- /package/templates/{common → base}/timsquad/generators/prd.xml +0 -0
- /package/templates/{common → base}/timsquad/generators/requirements.xml +0 -0
- /package/templates/{common → base}/timsquad/generators/service-spec.xml +0 -0
- /package/templates/{common → base}/timsquad/logs/_example.md +0 -0
- /package/templates/{common → base}/timsquad/logs/_template.md +0 -0
- /package/templates/{common → base}/timsquad/patterns/cqrs.xml +0 -0
- /package/templates/{common → base}/timsquad/patterns/event-sourcing.xml +0 -0
- /package/templates/{common → base}/timsquad/patterns/repository.xml +0 -0
- /package/templates/{common → base}/timsquad/process/validation-rules.xml +0 -0
- /package/templates/{common → base}/timsquad/retrospective/cycle-report.template.md +0 -0
- /package/templates/{common → base}/timsquad/retrospective/patterns/failure-patterns.md +0 -0
- /package/templates/{common → base}/timsquad/retrospective/patterns/success-patterns.md +0 -0
- /package/templates/{common → base}/timsquad/retrospective/retrospective-config.xml +0 -0
- /package/templates/{common → base}/timsquad/retrospective/retrospective-state.xml +0 -0
- /package/templates/{common → base}/timsquad/ssot/adr/ADR-000-template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/adr/ADR-001-example.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/data-design.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/deployment-spec.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/env-config.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/error-codes.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/functional-spec.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/glossary.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/integration-spec.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/planning.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/prd.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/requirements.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/security-spec.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/service-spec.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/test-spec.template.md +0 -0
- /package/templates/{common → base}/timsquad/ssot/ui-ux-spec.template.md +0 -0
- /package/templates/{common → base}/timsquad/state/workspace.xml +0 -0
package/dist/commands/log.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { colors, printHeader, printError, printSuccess, printKeyValue } from '../utils/colors.js';
|
|
3
4
|
import { findProjectRoot } from '../lib/project.js';
|
|
4
5
|
import { exists, readFile, writeFile, listFiles } from '../utils/fs.js';
|
|
5
|
-
import { getDateString, getTimeString } from '../utils/date.js';
|
|
6
|
+
import { getDateString, getTimeString, getTimestamp } from '../utils/date.js';
|
|
7
|
+
import { loadWorkflowState } from '../lib/workflow-state.js';
|
|
6
8
|
const LOG_TYPES = ['work', 'decision', 'error', 'feedback', 'handoff'];
|
|
9
|
+
const LOG_SCHEMA_VERSION = '1.0.0';
|
|
7
10
|
export function registerLogCommand(program) {
|
|
8
11
|
const logCmd = program
|
|
9
12
|
.command('log')
|
|
@@ -86,6 +89,188 @@ export function registerLogCommand(program) {
|
|
|
86
89
|
process.exit(1);
|
|
87
90
|
}
|
|
88
91
|
});
|
|
92
|
+
// tsq log compact
|
|
93
|
+
logCmd
|
|
94
|
+
.command('compact')
|
|
95
|
+
.description('Compress old logs (session JSONL → summary, merge old md)')
|
|
96
|
+
.option('--days <n>', 'Keep logs newer than N days', '7')
|
|
97
|
+
.option('--dry-run', 'Show what would be compacted without changes')
|
|
98
|
+
.action(async (options) => {
|
|
99
|
+
try {
|
|
100
|
+
await compactLogs(parseInt(options.days, 10), !!options.dryRun);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
printError(error.message);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
// ── tsq log enrich <agent> ──
|
|
108
|
+
logCmd
|
|
109
|
+
.command('enrich <agent>')
|
|
110
|
+
.description('Enrich latest task log with semantic data')
|
|
111
|
+
.requiredOption('--json <data>', 'Semantic data as JSON string')
|
|
112
|
+
.action(async (agent, options) => {
|
|
113
|
+
try {
|
|
114
|
+
await enrichTaskLog(agent, options.json);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
printError(error.message);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
// ── tsq log task <subcommand> ──
|
|
122
|
+
const taskCmd = logCmd
|
|
123
|
+
.command('task')
|
|
124
|
+
.description('L1 Task log management');
|
|
125
|
+
taskCmd
|
|
126
|
+
.command('list')
|
|
127
|
+
.description('List task logs')
|
|
128
|
+
.option('--agent <name>', 'Filter by agent')
|
|
129
|
+
.action(async (options) => {
|
|
130
|
+
try {
|
|
131
|
+
await listTaskLogs(options.agent);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
printError(error.message);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
taskCmd
|
|
139
|
+
.command('view <file>')
|
|
140
|
+
.description('View a task log')
|
|
141
|
+
.action(async (file) => {
|
|
142
|
+
try {
|
|
143
|
+
await viewTaskLog(file);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
printError(error.message);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
taskCmd
|
|
151
|
+
.command('stats')
|
|
152
|
+
.description('Task log statistics')
|
|
153
|
+
.action(async () => {
|
|
154
|
+
try {
|
|
155
|
+
await showTaskStats();
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
printError(error.message);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
// ── tsq log sequence <subcommand> ──
|
|
163
|
+
const seqCmd = logCmd
|
|
164
|
+
.command('sequence')
|
|
165
|
+
.description('L2 Sequence log management');
|
|
166
|
+
seqCmd
|
|
167
|
+
.command('list')
|
|
168
|
+
.description('List sequence logs')
|
|
169
|
+
.action(async () => {
|
|
170
|
+
try {
|
|
171
|
+
await listSequenceLogs();
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
printError(error.message);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
seqCmd
|
|
179
|
+
.command('view <seq-id>')
|
|
180
|
+
.description('View a sequence log')
|
|
181
|
+
.action(async (seqId) => {
|
|
182
|
+
try {
|
|
183
|
+
await viewSequenceLog(seqId);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
printError(error.message);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
seqCmd
|
|
191
|
+
.command('create <seq-id>')
|
|
192
|
+
.description('Create sequence log from task logs')
|
|
193
|
+
.requiredOption('--phase <id>', 'Phase ID')
|
|
194
|
+
.requiredOption('--report <path>', 'Architect report path')
|
|
195
|
+
.option('--verdict <v>', 'proceed or hold', 'proceed')
|
|
196
|
+
.option('--conditions <c>', 'Comma-separated conditions')
|
|
197
|
+
.action(async (seqId, options) => {
|
|
198
|
+
try {
|
|
199
|
+
await createSequenceLog(seqId, options);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
printError(error.message);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
seqCmd
|
|
207
|
+
.command('check <seq-id>')
|
|
208
|
+
.description('Check task log completeness for a sequence')
|
|
209
|
+
.action(async (seqId) => {
|
|
210
|
+
try {
|
|
211
|
+
await checkSequence(seqId);
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
printError(error.message);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
// ── tsq log phase <subcommand> ──
|
|
219
|
+
const phaseCmd = logCmd
|
|
220
|
+
.command('phase')
|
|
221
|
+
.description('L3 Phase log management');
|
|
222
|
+
phaseCmd
|
|
223
|
+
.command('list')
|
|
224
|
+
.description('List phase logs')
|
|
225
|
+
.action(async () => {
|
|
226
|
+
try {
|
|
227
|
+
await listPhaseLogs();
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
printError(error.message);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
phaseCmd
|
|
235
|
+
.command('view <phase-id>')
|
|
236
|
+
.description('View a phase log')
|
|
237
|
+
.action(async (phaseId) => {
|
|
238
|
+
try {
|
|
239
|
+
await viewPhaseLog(phaseId);
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
printError(error.message);
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
phaseCmd
|
|
247
|
+
.command('create <phase-id>')
|
|
248
|
+
.description('Create phase log')
|
|
249
|
+
.requiredOption('--sequences <ids>', 'Comma-separated sequence IDs')
|
|
250
|
+
.option('--retro-keep <items>', 'Retrospective: keep (comma-separated)')
|
|
251
|
+
.option('--retro-problem <items>', 'Retrospective: problems (comma-separated)')
|
|
252
|
+
.option('--retro-try <items>', 'Retrospective: try (comma-separated)')
|
|
253
|
+
.action(async (phaseId, options) => {
|
|
254
|
+
try {
|
|
255
|
+
await createPhaseLog(phaseId, options);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
printError(error.message);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
phaseCmd
|
|
263
|
+
.command('gate <phase-id>')
|
|
264
|
+
.description('Check phase transition gate')
|
|
265
|
+
.action(async (phaseId) => {
|
|
266
|
+
try {
|
|
267
|
+
await checkPhaseGate(phaseId);
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
printError(error.message);
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
89
274
|
}
|
|
90
275
|
async function addLog(agent, type, message) {
|
|
91
276
|
const projectRoot = await findProjectRoot();
|
|
@@ -268,4 +453,984 @@ async function readFromStdin() {
|
|
|
268
453
|
setTimeout(() => resolve(''), 100);
|
|
269
454
|
});
|
|
270
455
|
}
|
|
456
|
+
async function compactLogs(days, dryRun) {
|
|
457
|
+
const projectRoot = await findProjectRoot();
|
|
458
|
+
if (!projectRoot) {
|
|
459
|
+
throw new Error('Not a TimSquad project');
|
|
460
|
+
}
|
|
461
|
+
printHeader('Log Compact');
|
|
462
|
+
printKeyValue('Keep newer than', `${days} days`);
|
|
463
|
+
if (dryRun) {
|
|
464
|
+
console.log(colors.warning('DRY RUN - no files will be modified\n'));
|
|
465
|
+
}
|
|
466
|
+
const cutoffDate = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
|
467
|
+
const cutoffStr = cutoffDate.toISOString().split('T')[0];
|
|
468
|
+
let jsonlCompacted = 0;
|
|
469
|
+
let mdMerged = 0;
|
|
470
|
+
let bytesFreed = 0;
|
|
471
|
+
// ── 1. 세션 JSONL 압축 ──
|
|
472
|
+
const sessionsDir = path.join(projectRoot, '.timsquad', 'logs', 'sessions');
|
|
473
|
+
if (await exists(sessionsDir)) {
|
|
474
|
+
const jsonlFiles = await listFiles('*.jsonl', sessionsDir);
|
|
475
|
+
const oldJsonl = jsonlFiles.filter(f => {
|
|
476
|
+
const dateStr = f.split('-').slice(0, 3).join('-');
|
|
477
|
+
return dateStr < cutoffStr;
|
|
478
|
+
});
|
|
479
|
+
if (oldJsonl.length > 0) {
|
|
480
|
+
console.log(colors.subheader(`\n Session JSONL: ${oldJsonl.length} files to compact`));
|
|
481
|
+
const summaries = [];
|
|
482
|
+
for (const file of oldJsonl) {
|
|
483
|
+
const filePath = path.join(sessionsDir, file);
|
|
484
|
+
const stat = await fs.stat(filePath);
|
|
485
|
+
bytesFreed += stat.size;
|
|
486
|
+
try {
|
|
487
|
+
const content = await readFile(filePath);
|
|
488
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
489
|
+
let toolUses = 0;
|
|
490
|
+
let failures = 0;
|
|
491
|
+
let subagents = 0;
|
|
492
|
+
let tokenInput = 0;
|
|
493
|
+
let tokenOutput = 0;
|
|
494
|
+
let cacheCreate = 0;
|
|
495
|
+
let cacheRead = 0;
|
|
496
|
+
for (const line of lines) {
|
|
497
|
+
try {
|
|
498
|
+
const ev = JSON.parse(line);
|
|
499
|
+
if (ev.event === 'PostToolUse')
|
|
500
|
+
toolUses++;
|
|
501
|
+
if (ev.event === 'PostToolUseFailure')
|
|
502
|
+
failures++;
|
|
503
|
+
if (ev.event === 'SubagentStart')
|
|
504
|
+
subagents++;
|
|
505
|
+
if (ev.event === 'SessionEnd' && ev.total_usage) {
|
|
506
|
+
tokenInput += ev.total_usage.total_input || 0;
|
|
507
|
+
tokenOutput += ev.total_usage.total_output || 0;
|
|
508
|
+
cacheCreate += ev.total_usage.total_cache_create || 0;
|
|
509
|
+
cacheRead += ev.total_usage.total_cache_read || 0;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
catch { /* skip */ }
|
|
513
|
+
}
|
|
514
|
+
const dateStr = file.split('-').slice(0, 3).join('-');
|
|
515
|
+
const sessionId = file.replace(/\.jsonl$/, '').split('-').slice(3).join('-');
|
|
516
|
+
summaries.push({
|
|
517
|
+
file,
|
|
518
|
+
date: dateStr,
|
|
519
|
+
session: sessionId,
|
|
520
|
+
events: lines.length,
|
|
521
|
+
toolUses,
|
|
522
|
+
failures,
|
|
523
|
+
subagents,
|
|
524
|
+
tokens: { input: tokenInput, output: tokenOutput, cacheCreate, cacheRead },
|
|
525
|
+
});
|
|
526
|
+
if (!dryRun) {
|
|
527
|
+
await fs.remove(filePath);
|
|
528
|
+
}
|
|
529
|
+
jsonlCompacted++;
|
|
530
|
+
console.log(colors.dim(` ${dryRun ? '[dry-run] ' : ''}${file} (${lines.length} events, ${formatSize(stat.size)})`));
|
|
531
|
+
}
|
|
532
|
+
catch {
|
|
533
|
+
// skip unreadable files
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
// 압축 요약 저장
|
|
537
|
+
if (!dryRun && summaries.length > 0) {
|
|
538
|
+
const archiveDir = path.join(sessionsDir, 'archive');
|
|
539
|
+
await fs.ensureDir(archiveDir);
|
|
540
|
+
const monthGroups = {};
|
|
541
|
+
for (const s of summaries) {
|
|
542
|
+
const month = s.date.slice(0, 7); // YYYY-MM
|
|
543
|
+
if (!monthGroups[month])
|
|
544
|
+
monthGroups[month] = [];
|
|
545
|
+
monthGroups[month].push(s);
|
|
546
|
+
}
|
|
547
|
+
for (const [month, group] of Object.entries(monthGroups)) {
|
|
548
|
+
const archivePath = path.join(archiveDir, `${month}-summary.json`);
|
|
549
|
+
let existing = [];
|
|
550
|
+
if (await exists(archivePath)) {
|
|
551
|
+
try {
|
|
552
|
+
const data = await fs.readJson(archivePath);
|
|
553
|
+
existing = data.sessions || [];
|
|
554
|
+
}
|
|
555
|
+
catch { /* start fresh */ }
|
|
556
|
+
}
|
|
557
|
+
await fs.writeJson(archivePath, {
|
|
558
|
+
compactedAt: getTimestamp(),
|
|
559
|
+
month,
|
|
560
|
+
sessions: [...existing, ...group],
|
|
561
|
+
totals: {
|
|
562
|
+
sessions: existing.length + group.length,
|
|
563
|
+
events: [...existing, ...group].reduce((a, s) => a + s.events, 0),
|
|
564
|
+
toolUses: [...existing, ...group].reduce((a, s) => a + s.toolUses, 0),
|
|
565
|
+
failures: [...existing, ...group].reduce((a, s) => a + s.failures, 0),
|
|
566
|
+
},
|
|
567
|
+
}, { spaces: 2 });
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
// ── 2. 작업 로그 MD 병합 ──
|
|
573
|
+
const logsDir = path.join(projectRoot, '.timsquad', 'logs');
|
|
574
|
+
if (await exists(logsDir)) {
|
|
575
|
+
const mdFiles = await listFiles('????-??-??-*.md', logsDir);
|
|
576
|
+
const oldMd = mdFiles.filter(f => {
|
|
577
|
+
const match = f.match(/^(\d{4}-\d{2}-\d{2})-/);
|
|
578
|
+
return match && match[1] < cutoffStr;
|
|
579
|
+
});
|
|
580
|
+
if (oldMd.length > 0) {
|
|
581
|
+
console.log(colors.subheader(`\n Work Logs MD: ${oldMd.length} files to merge`));
|
|
582
|
+
// 월별 그룹핑
|
|
583
|
+
const monthGroups = {};
|
|
584
|
+
for (const file of oldMd) {
|
|
585
|
+
const month = file.slice(0, 7);
|
|
586
|
+
if (!monthGroups[month])
|
|
587
|
+
monthGroups[month] = [];
|
|
588
|
+
monthGroups[month].push(file);
|
|
589
|
+
}
|
|
590
|
+
for (const [month, files] of Object.entries(monthGroups)) {
|
|
591
|
+
const mergedPath = path.join(logsDir, `${month}-archive.md`);
|
|
592
|
+
let mergedContent = '';
|
|
593
|
+
if (await exists(mergedPath)) {
|
|
594
|
+
mergedContent = await readFile(mergedPath);
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
mergedContent = `# Archive - ${month}\n\n> ${files.length} log files merged by tsq log compact\n\n---\n`;
|
|
598
|
+
}
|
|
599
|
+
for (const file of files.sort()) {
|
|
600
|
+
const filePath = path.join(logsDir, file);
|
|
601
|
+
const stat = await fs.stat(filePath);
|
|
602
|
+
bytesFreed += stat.size;
|
|
603
|
+
try {
|
|
604
|
+
const content = await readFile(filePath);
|
|
605
|
+
mergedContent += `\n\n<!-- ${file} -->\n${content}`;
|
|
606
|
+
if (!dryRun) {
|
|
607
|
+
await fs.remove(filePath);
|
|
608
|
+
}
|
|
609
|
+
mdMerged++;
|
|
610
|
+
console.log(colors.dim(` ${dryRun ? '[dry-run] ' : ''}${file} → ${month}-archive.md`));
|
|
611
|
+
}
|
|
612
|
+
catch { /* skip */ }
|
|
613
|
+
}
|
|
614
|
+
if (!dryRun) {
|
|
615
|
+
await writeFile(mergedPath, mergedContent);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// ── 결과 ──
|
|
621
|
+
console.log('');
|
|
622
|
+
if (jsonlCompacted + mdMerged === 0) {
|
|
623
|
+
console.log(colors.dim('No files older than threshold. Nothing to compact.'));
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
printSuccess(`Compacted: ${jsonlCompacted} JSONL + ${mdMerged} MD files`);
|
|
627
|
+
printKeyValue('Space freed', `~${formatSize(bytesFreed)}`);
|
|
628
|
+
if (dryRun) {
|
|
629
|
+
console.log(colors.dim('\nRun without --dry-run to apply changes.'));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function formatSize(bytes) {
|
|
634
|
+
if (bytes < 1024)
|
|
635
|
+
return `${bytes}B`;
|
|
636
|
+
if (bytes < 1024 * 1024)
|
|
637
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
638
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
639
|
+
}
|
|
640
|
+
// ============================================================
|
|
641
|
+
// Task Log Helpers
|
|
642
|
+
// ============================================================
|
|
643
|
+
export function getTasksDir(projectRoot) {
|
|
644
|
+
return path.join(projectRoot, '.timsquad', 'logs', 'tasks');
|
|
645
|
+
}
|
|
646
|
+
export function getSequencesDir(projectRoot) {
|
|
647
|
+
return path.join(projectRoot, '.timsquad', 'logs', 'sequences');
|
|
648
|
+
}
|
|
649
|
+
export function getPhasesDir(projectRoot) {
|
|
650
|
+
return path.join(projectRoot, '.timsquad', 'logs', 'phases');
|
|
651
|
+
}
|
|
652
|
+
async function findLatestTaskLog(projectRoot, agent) {
|
|
653
|
+
const tasksDir = getTasksDir(projectRoot);
|
|
654
|
+
if (!await exists(tasksDir))
|
|
655
|
+
return null;
|
|
656
|
+
// Flat structure: *-{agent}.json
|
|
657
|
+
const flatFiles = await listFiles(`*-${agent}.json`, tasksDir);
|
|
658
|
+
// Nested structure: look inside subdirectories
|
|
659
|
+
const nestedFiles = [];
|
|
660
|
+
try {
|
|
661
|
+
const entries = await fs.readdir(tasksDir, { withFileTypes: true });
|
|
662
|
+
for (const entry of entries) {
|
|
663
|
+
if (entry.isDirectory()) {
|
|
664
|
+
const subFiles = await listFiles(`*-${agent}.json`, path.join(tasksDir, entry.name));
|
|
665
|
+
nestedFiles.push(...subFiles.map(f => path.join(entry.name, f)));
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
catch { /* no subdirectories */ }
|
|
670
|
+
const allFiles = [...flatFiles, ...nestedFiles].sort().reverse();
|
|
671
|
+
return allFiles.length > 0 ? allFiles[0] : null;
|
|
672
|
+
}
|
|
673
|
+
export async function loadAllTaskLogs(projectRoot, agent) {
|
|
674
|
+
const tasksDir = getTasksDir(projectRoot);
|
|
675
|
+
if (!await exists(tasksDir))
|
|
676
|
+
return [];
|
|
677
|
+
const pattern = agent ? `*-${agent}.json` : '*.json';
|
|
678
|
+
const files = await listFiles(pattern, tasksDir);
|
|
679
|
+
const results = [];
|
|
680
|
+
for (const file of files) {
|
|
681
|
+
try {
|
|
682
|
+
const data = await fs.readJson(path.join(tasksDir, file));
|
|
683
|
+
results.push({ file, data });
|
|
684
|
+
}
|
|
685
|
+
catch { /* skip malformed */ }
|
|
686
|
+
}
|
|
687
|
+
return results.sort((a, b) => a.file.localeCompare(b.file));
|
|
688
|
+
}
|
|
689
|
+
function mergeSemantic(existing, incoming) {
|
|
690
|
+
return {
|
|
691
|
+
summary: incoming.summary ?? existing.summary,
|
|
692
|
+
techniques: incoming.techniques ?? existing.techniques,
|
|
693
|
+
ssot_refs: incoming.ssot_refs ?? existing.ssot_refs,
|
|
694
|
+
decisions: incoming.decisions ?? existing.decisions,
|
|
695
|
+
issues: incoming.issues ?? existing.issues,
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
export function hasSemantic(sem) {
|
|
699
|
+
if (!sem)
|
|
700
|
+
return false;
|
|
701
|
+
return !!(sem.summary || sem.techniques?.length || sem.ssot_refs?.length ||
|
|
702
|
+
sem.decisions?.length || sem.issues?.length);
|
|
703
|
+
}
|
|
704
|
+
// ============================================================
|
|
705
|
+
// Step 2: tsq log enrich
|
|
706
|
+
// ============================================================
|
|
707
|
+
async function enrichTaskLog(agent, jsonData) {
|
|
708
|
+
const projectRoot = await findProjectRoot();
|
|
709
|
+
if (!projectRoot)
|
|
710
|
+
throw new Error('Not a TimSquad project');
|
|
711
|
+
const latestFile = await findLatestTaskLog(projectRoot, agent);
|
|
712
|
+
if (!latestFile) {
|
|
713
|
+
throw new Error(`No task log found for agent: ${agent}`);
|
|
714
|
+
}
|
|
715
|
+
const filePath = path.join(getTasksDir(projectRoot), latestFile);
|
|
716
|
+
let incoming;
|
|
717
|
+
try {
|
|
718
|
+
incoming = JSON.parse(jsonData);
|
|
719
|
+
}
|
|
720
|
+
catch {
|
|
721
|
+
throw new Error('Invalid JSON data');
|
|
722
|
+
}
|
|
723
|
+
const taskLog = await fs.readJson(filePath);
|
|
724
|
+
taskLog.semantic = mergeSemantic(taskLog.semantic || {}, incoming);
|
|
725
|
+
await fs.writeJson(filePath, taskLog, { spaces: 2 });
|
|
726
|
+
printSuccess(`Enriched: ${latestFile}`);
|
|
727
|
+
if (taskLog.semantic.summary) {
|
|
728
|
+
printKeyValue('Summary', taskLog.semantic.summary);
|
|
729
|
+
}
|
|
730
|
+
if (taskLog.semantic.techniques?.length) {
|
|
731
|
+
printKeyValue('Techniques', taskLog.semantic.techniques.map(t => t.name).join(', '));
|
|
732
|
+
}
|
|
733
|
+
// Auto-feedback: L2/L3 이슈 → 자동 피드백 라우팅
|
|
734
|
+
await autoFeedbackFromIssues(projectRoot, taskLog.semantic);
|
|
735
|
+
}
|
|
736
|
+
// ============================================================
|
|
737
|
+
// Step 3: tsq log task (L1 views)
|
|
738
|
+
// ============================================================
|
|
739
|
+
async function listTaskLogs(agent) {
|
|
740
|
+
const projectRoot = await findProjectRoot();
|
|
741
|
+
if (!projectRoot)
|
|
742
|
+
throw new Error('Not a TimSquad project');
|
|
743
|
+
const logs = await loadAllTaskLogs(projectRoot, agent);
|
|
744
|
+
if (logs.length === 0) {
|
|
745
|
+
console.log(colors.dim('No task logs found'));
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
printHeader(`Task Logs (${logs.length})`);
|
|
749
|
+
for (const { file, data } of logs.reverse()) {
|
|
750
|
+
const sem = hasSemantic(data.semantic) ? '✓' : '○';
|
|
751
|
+
const status = data.status === 'completed' ? colors.success('✓') : colors.error('✗');
|
|
752
|
+
const agentName = colors.agent(data.agent || '?');
|
|
753
|
+
const date = file.slice(0, 10);
|
|
754
|
+
console.log(` ${status} ${sem} ${colors.dim(date)} ${agentName} ${colors.dim(file)}`);
|
|
755
|
+
if (data.semantic?.summary) {
|
|
756
|
+
console.log(` ${colors.dim(data.semantic.summary.slice(0, 80))}`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
async function viewTaskLog(file) {
|
|
761
|
+
const projectRoot = await findProjectRoot();
|
|
762
|
+
if (!projectRoot)
|
|
763
|
+
throw new Error('Not a TimSquad project');
|
|
764
|
+
const filePath = path.join(getTasksDir(projectRoot), file);
|
|
765
|
+
if (!await exists(filePath)) {
|
|
766
|
+
throw new Error(`Task log not found: ${file}`);
|
|
767
|
+
}
|
|
768
|
+
const data = await fs.readJson(filePath);
|
|
769
|
+
printHeader(`Task Log: ${file}`);
|
|
770
|
+
// Basic info
|
|
771
|
+
printKeyValue('Agent', data.agent);
|
|
772
|
+
printKeyValue('Status', data.status);
|
|
773
|
+
printKeyValue('Completed', data.completed_at || 'N/A');
|
|
774
|
+
if (data.duration_ms)
|
|
775
|
+
printKeyValue('Duration', `${(data.duration_ms / 1000).toFixed(1)}s`);
|
|
776
|
+
// Mechanical
|
|
777
|
+
console.log(colors.subheader('\n Mechanical'));
|
|
778
|
+
if (data.mechanical?.files?.length) {
|
|
779
|
+
for (const f of data.mechanical.files) {
|
|
780
|
+
console.log(` ${f.action}: ${colors.path(f.path)}`);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
console.log(colors.dim(' (no file changes)'));
|
|
785
|
+
}
|
|
786
|
+
// Semantic
|
|
787
|
+
console.log(colors.subheader('\n Semantic'));
|
|
788
|
+
if (hasSemantic(data.semantic)) {
|
|
789
|
+
if (data.semantic.summary)
|
|
790
|
+
printKeyValue(' Summary', data.semantic.summary);
|
|
791
|
+
if (data.semantic.techniques?.length) {
|
|
792
|
+
console.log(' Techniques:');
|
|
793
|
+
for (const t of data.semantic.techniques) {
|
|
794
|
+
console.log(` - ${t.name}: ${colors.dim(t.reason)}`);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (data.semantic.ssot_refs?.length) {
|
|
798
|
+
console.log(' SSOT Refs:');
|
|
799
|
+
for (const r of data.semantic.ssot_refs) {
|
|
800
|
+
const icon = r.status === 'aligned' ? '✓' : r.status === 'misaligned' ? '✗' : '~';
|
|
801
|
+
console.log(` ${icon} ${r.doc} → ${r.section} (${r.status})`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
if (data.semantic.decisions?.length) {
|
|
805
|
+
console.log(' Decisions:');
|
|
806
|
+
for (const d of data.semantic.decisions) {
|
|
807
|
+
console.log(` - ${d.decision}`);
|
|
808
|
+
console.log(` ${colors.dim(d.rationale)}`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (data.semantic.issues?.length) {
|
|
812
|
+
console.log(' Issues:');
|
|
813
|
+
for (const i of data.semantic.issues) {
|
|
814
|
+
console.log(` L${i.level}: ${i.description}`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
console.log(colors.dim(' (empty — run tsq log enrich to populate)'));
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
async function showTaskStats() {
|
|
823
|
+
const projectRoot = await findProjectRoot();
|
|
824
|
+
if (!projectRoot)
|
|
825
|
+
throw new Error('Not a TimSquad project');
|
|
826
|
+
const logs = await loadAllTaskLogs(projectRoot);
|
|
827
|
+
if (logs.length === 0) {
|
|
828
|
+
console.log(colors.dim('No task logs found'));
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const stats = {
|
|
832
|
+
total: logs.length,
|
|
833
|
+
completed: 0, failed: 0, successRate: 0,
|
|
834
|
+
byAgent: {},
|
|
835
|
+
totalFilesChanged: 0, avgFilesPerTask: 0, fileActions: {},
|
|
836
|
+
withErrors: 0, errorTypes: {},
|
|
837
|
+
withSemantic: 0, semanticCoverage: 0,
|
|
838
|
+
};
|
|
839
|
+
for (const { data } of logs) {
|
|
840
|
+
// Status
|
|
841
|
+
if (data.status === 'completed' || data.status === 'success')
|
|
842
|
+
stats.completed++;
|
|
843
|
+
else
|
|
844
|
+
stats.failed++;
|
|
845
|
+
// By agent
|
|
846
|
+
const a = data.agent || 'unknown';
|
|
847
|
+
if (!stats.byAgent[a])
|
|
848
|
+
stats.byAgent[a] = { total: 0, completed: 0, failed: 0 };
|
|
849
|
+
stats.byAgent[a].total++;
|
|
850
|
+
if (data.status === 'completed' || data.status === 'success')
|
|
851
|
+
stats.byAgent[a].completed++;
|
|
852
|
+
else
|
|
853
|
+
stats.byAgent[a].failed++;
|
|
854
|
+
// Files
|
|
855
|
+
const files = data.mechanical?.files || [];
|
|
856
|
+
stats.totalFilesChanged += files.length;
|
|
857
|
+
for (const f of files) {
|
|
858
|
+
stats.fileActions[f.action] = (stats.fileActions[f.action] || 0) + 1;
|
|
859
|
+
}
|
|
860
|
+
// Errors
|
|
861
|
+
if (data.error) {
|
|
862
|
+
stats.withErrors++;
|
|
863
|
+
const t = data.error.type || 'unknown';
|
|
864
|
+
stats.errorTypes[t] = (stats.errorTypes[t] || 0) + 1;
|
|
865
|
+
}
|
|
866
|
+
// Semantic
|
|
867
|
+
if (hasSemantic(data.semantic))
|
|
868
|
+
stats.withSemantic++;
|
|
869
|
+
}
|
|
870
|
+
stats.successRate = Math.round((stats.completed / stats.total) * 100);
|
|
871
|
+
stats.avgFilesPerTask = Math.round((stats.totalFilesChanged / stats.total) * 10) / 10;
|
|
872
|
+
stats.semanticCoverage = Math.round((stats.withSemantic / stats.total) * 100);
|
|
873
|
+
printHeader('Task Log Statistics');
|
|
874
|
+
printKeyValue('Total Tasks', String(stats.total));
|
|
875
|
+
printKeyValue('Success Rate', `${stats.successRate}% (${stats.completed}/${stats.total})`);
|
|
876
|
+
printKeyValue('Semantic Coverage', `${stats.semanticCoverage}% (${stats.withSemantic}/${stats.total})`);
|
|
877
|
+
printKeyValue('Files Changed', `${stats.totalFilesChanged} (avg ${stats.avgFilesPerTask}/task)`);
|
|
878
|
+
if (Object.keys(stats.byAgent).length > 1) {
|
|
879
|
+
console.log(colors.subheader('\n By Agent'));
|
|
880
|
+
for (const [agent, s] of Object.entries(stats.byAgent)) {
|
|
881
|
+
const rate = Math.round((s.completed / s.total) * 100);
|
|
882
|
+
console.log(` ${colors.agent(agent)}: ${s.total} tasks, ${rate}% success`);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
if (stats.withErrors > 0) {
|
|
886
|
+
console.log(colors.subheader('\n Errors'));
|
|
887
|
+
printKeyValue(' Tasks with errors', String(stats.withErrors));
|
|
888
|
+
for (const [type, count] of Object.entries(stats.errorTypes)) {
|
|
889
|
+
console.log(` ${type}: ${count}`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
// ============================================================
|
|
894
|
+
// Step 4: tsq log sequence (L2)
|
|
895
|
+
// ============================================================
|
|
896
|
+
export async function loadSequenceTaskLogs(projectRoot, seqId) {
|
|
897
|
+
const tasksDir = getTasksDir(projectRoot);
|
|
898
|
+
if (!await exists(tasksDir))
|
|
899
|
+
return [];
|
|
900
|
+
const results = [];
|
|
901
|
+
// 1. Check nested directory: tasks/{SEQ-ID}/*.json
|
|
902
|
+
const seqDir = path.join(tasksDir, seqId);
|
|
903
|
+
if (await exists(seqDir)) {
|
|
904
|
+
const files = await listFiles('*.json', seqDir);
|
|
905
|
+
for (const file of files) {
|
|
906
|
+
try {
|
|
907
|
+
const data = await fs.readJson(path.join(seqDir, file));
|
|
908
|
+
results.push({ file: path.join(seqId, file), data });
|
|
909
|
+
}
|
|
910
|
+
catch { /* skip */ }
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
// 2. Check flat files with trace.sequence_id matching
|
|
914
|
+
const flatFiles = await listFiles('*.json', tasksDir);
|
|
915
|
+
for (const file of flatFiles) {
|
|
916
|
+
try {
|
|
917
|
+
const data = await fs.readJson(path.join(tasksDir, file));
|
|
918
|
+
if (data.trace?.sequence_id === seqId) {
|
|
919
|
+
// Avoid duplicates from nested
|
|
920
|
+
if (!results.some(r => r.file === file)) {
|
|
921
|
+
results.push({ file, data });
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
catch { /* skip */ }
|
|
926
|
+
}
|
|
927
|
+
return results.sort((a, b) => a.file.localeCompare(b.file));
|
|
928
|
+
}
|
|
929
|
+
function aggregateSequenceStats(taskLogs) {
|
|
930
|
+
let success = 0, failure = 0, error = 0, rework = 0;
|
|
931
|
+
const durations = [];
|
|
932
|
+
for (const { file, data } of taskLogs) {
|
|
933
|
+
const s = data.status;
|
|
934
|
+
if (s === 'completed' || s === 'success')
|
|
935
|
+
success++;
|
|
936
|
+
else if (s === 'failure')
|
|
937
|
+
failure++;
|
|
938
|
+
else if (s === 'error')
|
|
939
|
+
error++;
|
|
940
|
+
if (data.duration_ms)
|
|
941
|
+
durations.push(data.duration_ms);
|
|
942
|
+
// Simple rework detection: same agent + file overlap with earlier task
|
|
943
|
+
const changedFiles = new Set(data.mechanical?.files?.map(f => f.path) || []);
|
|
944
|
+
for (const other of taskLogs) {
|
|
945
|
+
if (other.file === file)
|
|
946
|
+
continue;
|
|
947
|
+
if (other.data.agent === data.agent && other.file < file) {
|
|
948
|
+
const otherFiles = other.data.mechanical?.files?.map(f => f.path) || [];
|
|
949
|
+
if (otherFiles.some(f => changedFiles.has(f))) {
|
|
950
|
+
rework++;
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
const total = taskLogs.length;
|
|
957
|
+
return {
|
|
958
|
+
total, success, failure, error, rework,
|
|
959
|
+
first_pass_success_rate: total > 0 ? Math.round(((success) / total) * 100) / 100 : 0,
|
|
960
|
+
final_success_rate: total > 0 ? Math.round(((success) / total) * 100) / 100 : 0,
|
|
961
|
+
durations,
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
export function makeAxisPlaceholder() {
|
|
965
|
+
return { verdict: 'n/a', details: 'See architect report', issues: [] };
|
|
966
|
+
}
|
|
967
|
+
async function listSequenceLogs() {
|
|
968
|
+
const projectRoot = await findProjectRoot();
|
|
969
|
+
if (!projectRoot)
|
|
970
|
+
throw new Error('Not a TimSquad project');
|
|
971
|
+
const seqDir = getSequencesDir(projectRoot);
|
|
972
|
+
if (!await exists(seqDir)) {
|
|
973
|
+
console.log(colors.dim('No sequence logs found'));
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
const files = await listFiles('*.json', seqDir);
|
|
977
|
+
if (files.length === 0) {
|
|
978
|
+
console.log(colors.dim('No sequence logs found'));
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
printHeader(`Sequence Logs (${files.length})`);
|
|
982
|
+
for (const file of files.sort()) {
|
|
983
|
+
try {
|
|
984
|
+
const data = await fs.readJson(path.join(seqDir, file));
|
|
985
|
+
const seqId = data.trace.sequence_id;
|
|
986
|
+
const status = data.execution.status;
|
|
987
|
+
const verdict = data.verdict.proceed ? colors.success('PROCEED') : colors.error('HOLD');
|
|
988
|
+
const tasks = `${data.tasks.success}/${data.tasks.total} tasks`;
|
|
989
|
+
console.log(` ${verdict} ${colors.agent(seqId)} [${status}] ${tasks} ${colors.dim(data.trace.phase_id)}`);
|
|
990
|
+
}
|
|
991
|
+
catch {
|
|
992
|
+
console.log(` ${colors.dim(file)} (unreadable)`);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
async function viewSequenceLog(seqId) {
|
|
997
|
+
const projectRoot = await findProjectRoot();
|
|
998
|
+
if (!projectRoot)
|
|
999
|
+
throw new Error('Not a TimSquad project');
|
|
1000
|
+
const filePath = path.join(getSequencesDir(projectRoot), `${seqId}.json`);
|
|
1001
|
+
if (!await exists(filePath)) {
|
|
1002
|
+
throw new Error(`Sequence log not found: ${seqId}`);
|
|
1003
|
+
}
|
|
1004
|
+
const data = await fs.readJson(filePath);
|
|
1005
|
+
printHeader(`Sequence: ${seqId}`);
|
|
1006
|
+
printKeyValue('Phase', data.trace.phase_id);
|
|
1007
|
+
printKeyValue('Status', data.execution.status);
|
|
1008
|
+
printKeyValue('Duration', `${(data.execution.duration_ms / 1000).toFixed(1)}s`);
|
|
1009
|
+
printKeyValue('Verdict', data.verdict.proceed ? 'PROCEED' : 'HOLD');
|
|
1010
|
+
console.log(colors.subheader('\n Tasks'));
|
|
1011
|
+
printKeyValue(' Total', String(data.tasks.total));
|
|
1012
|
+
printKeyValue(' Success', String(data.tasks.success));
|
|
1013
|
+
printKeyValue(' Failure', String(data.tasks.failure));
|
|
1014
|
+
printKeyValue(' Rework', String(data.tasks.rework));
|
|
1015
|
+
printKeyValue(' Success Rate', `${(data.tasks.final_success_rate * 100).toFixed(0)}%`);
|
|
1016
|
+
console.log(colors.subheader('\n Analysis'));
|
|
1017
|
+
for (const [axis, result] of Object.entries(data.analysis)) {
|
|
1018
|
+
const r = result;
|
|
1019
|
+
const icon = r.verdict === 'pass' ? '✓' : r.verdict === 'fail' ? '✗' : r.verdict === 'warn' ? '!' : '–';
|
|
1020
|
+
console.log(` ${icon} ${axis}: ${r.verdict} — ${colors.dim(r.details)}`);
|
|
1021
|
+
}
|
|
1022
|
+
console.log(colors.subheader('\n DORA'));
|
|
1023
|
+
printKeyValue(' Change Failure Rate', `${(data.dora_derived.change_failure_rate * 100).toFixed(0)}%`);
|
|
1024
|
+
printKeyValue(' Rework Rate', `${(data.dora_derived.rework_rate * 100).toFixed(0)}%`);
|
|
1025
|
+
printKeyValue(' Mean Task Duration', `${(data.dora_derived.mean_task_duration_ms / 1000).toFixed(1)}s`);
|
|
1026
|
+
if (data.verdict.conditions.length > 0) {
|
|
1027
|
+
console.log(colors.subheader('\n Conditions'));
|
|
1028
|
+
for (const c of data.verdict.conditions) {
|
|
1029
|
+
console.log(` - ${c}`);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
printKeyValue('\n Report', data.verdict.report_path);
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Core: build SequenceLogEntry data (no I/O side effects except reading task logs)
|
|
1036
|
+
*/
|
|
1037
|
+
export async function buildSequenceLogData(projectRoot, seqId, options) {
|
|
1038
|
+
const taskLogs = await loadSequenceTaskLogs(projectRoot, seqId);
|
|
1039
|
+
if (taskLogs.length === 0) {
|
|
1040
|
+
const tasksDir = getTasksDir(projectRoot);
|
|
1041
|
+
if (await exists(tasksDir)) {
|
|
1042
|
+
const allFiles = await listFiles('*.json', tasksDir);
|
|
1043
|
+
for (const file of allFiles) {
|
|
1044
|
+
if (file.toLowerCase().includes(seqId.toLowerCase())) {
|
|
1045
|
+
try {
|
|
1046
|
+
const data = await fs.readJson(path.join(tasksDir, file));
|
|
1047
|
+
taskLogs.push({ file, data });
|
|
1048
|
+
}
|
|
1049
|
+
catch { /* skip */ }
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
const stats = aggregateSequenceStats(taskLogs);
|
|
1055
|
+
const now = getTimestamp();
|
|
1056
|
+
const timestamps = taskLogs
|
|
1057
|
+
.map(t => t.data.completed_at ? new Date(t.data.completed_at).getTime() : 0)
|
|
1058
|
+
.filter(t => t > 0);
|
|
1059
|
+
const startedAt = taskLogs[0]?.data.started_at || taskLogs[0]?.data.completed_at || now;
|
|
1060
|
+
const duration = timestamps.length >= 2
|
|
1061
|
+
? Math.max(...timestamps) - Math.min(...timestamps)
|
|
1062
|
+
: 0;
|
|
1063
|
+
const meanDuration = stats.durations.length > 0
|
|
1064
|
+
? stats.durations.reduce((a, b) => a + b, 0) / stats.durations.length
|
|
1065
|
+
: 0;
|
|
1066
|
+
return {
|
|
1067
|
+
schema_version: LOG_SCHEMA_VERSION,
|
|
1068
|
+
trace: { phase_id: options.phase, sequence_id: seqId },
|
|
1069
|
+
execution: {
|
|
1070
|
+
status: stats.failure > 0 ? 'partial' : 'completed',
|
|
1071
|
+
started_at: startedAt,
|
|
1072
|
+
completed_at: now,
|
|
1073
|
+
duration_ms: duration,
|
|
1074
|
+
},
|
|
1075
|
+
tasks: {
|
|
1076
|
+
total: stats.total,
|
|
1077
|
+
success: stats.success,
|
|
1078
|
+
failure: stats.failure,
|
|
1079
|
+
error: stats.error,
|
|
1080
|
+
rework: stats.rework,
|
|
1081
|
+
first_pass_success_rate: stats.first_pass_success_rate,
|
|
1082
|
+
final_success_rate: stats.final_success_rate,
|
|
1083
|
+
},
|
|
1084
|
+
analysis: {
|
|
1085
|
+
axis_1_consistency: makeAxisPlaceholder(),
|
|
1086
|
+
axis_2_ssot_conformance: makeAxisPlaceholder(),
|
|
1087
|
+
axis_3_cross_sequence: { ...makeAxisPlaceholder(), prev_sequence: null },
|
|
1088
|
+
},
|
|
1089
|
+
dora_derived: {
|
|
1090
|
+
change_failure_rate: stats.total > 0 ? stats.failure / stats.total : 0,
|
|
1091
|
+
rework_rate: stats.total > 0 ? stats.rework / stats.total : 0,
|
|
1092
|
+
mean_task_duration_ms: meanDuration,
|
|
1093
|
+
recovery_time_ms: null,
|
|
1094
|
+
},
|
|
1095
|
+
verdict: {
|
|
1096
|
+
proceed: options.verdict !== 'hold',
|
|
1097
|
+
conditions: options.conditions ? options.conditions.split(',').map(c => c.trim()) : [],
|
|
1098
|
+
report_path: options.report,
|
|
1099
|
+
},
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Auto-feedback: L2/L3 이슈가 enrich로 들어오면 자동 피드백 라우팅
|
|
1104
|
+
*/
|
|
1105
|
+
async function autoFeedbackFromIssues(projectRoot, semantic) {
|
|
1106
|
+
const issues = semantic.issues || [];
|
|
1107
|
+
const l2l3Issues = issues.filter(i => i.level >= 2);
|
|
1108
|
+
if (l2l3Issues.length === 0)
|
|
1109
|
+
return;
|
|
1110
|
+
try {
|
|
1111
|
+
const state = await loadWorkflowState(projectRoot);
|
|
1112
|
+
if (!state.automation.feedback)
|
|
1113
|
+
return;
|
|
1114
|
+
for (const issue of l2l3Issues) {
|
|
1115
|
+
try {
|
|
1116
|
+
const { execSync } = await import('child_process');
|
|
1117
|
+
execSync(`npx tsq feedback route "${issue.description.replace(/"/g, '\\"')}"`, { cwd: projectRoot, timeout: 10000, stdio: 'ignore' });
|
|
1118
|
+
}
|
|
1119
|
+
catch { /* 피드백 라우팅 실패 무시 */ }
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
catch { /* 자동 피드백 실패 무시 */ }
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Core: build PhaseGateResult (no I/O side effects except reading sequence logs)
|
|
1126
|
+
*/
|
|
1127
|
+
export async function buildPhaseGateData(projectRoot, phaseId) {
|
|
1128
|
+
const seqDir = getSequencesDir(projectRoot);
|
|
1129
|
+
const result = {
|
|
1130
|
+
phase_id: phaseId,
|
|
1131
|
+
can_transition: true,
|
|
1132
|
+
missing_sequences: [],
|
|
1133
|
+
missing_reports: [],
|
|
1134
|
+
blocking_conditions: [],
|
|
1135
|
+
};
|
|
1136
|
+
if (!await exists(seqDir)) {
|
|
1137
|
+
result.can_transition = false;
|
|
1138
|
+
result.blocking_conditions.push('No sequence logs directory');
|
|
1139
|
+
return result;
|
|
1140
|
+
}
|
|
1141
|
+
const files = await listFiles('*.json', seqDir);
|
|
1142
|
+
const phaseSeqs = [];
|
|
1143
|
+
for (const file of files) {
|
|
1144
|
+
try {
|
|
1145
|
+
const data = await fs.readJson(path.join(seqDir, file));
|
|
1146
|
+
if (data.trace.phase_id === phaseId) {
|
|
1147
|
+
phaseSeqs.push(data);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
catch { /* skip */ }
|
|
1151
|
+
}
|
|
1152
|
+
if (phaseSeqs.length === 0) {
|
|
1153
|
+
result.can_transition = false;
|
|
1154
|
+
result.blocking_conditions.push('No sequence logs found for this phase');
|
|
1155
|
+
return result;
|
|
1156
|
+
}
|
|
1157
|
+
for (const seq of phaseSeqs) {
|
|
1158
|
+
if (!seq.verdict.proceed) {
|
|
1159
|
+
result.can_transition = false;
|
|
1160
|
+
result.blocking_conditions.push(`${seq.trace.sequence_id}: verdict is HOLD`);
|
|
1161
|
+
}
|
|
1162
|
+
if (seq.verdict.report_path) {
|
|
1163
|
+
const reportPath = path.resolve(projectRoot, seq.verdict.report_path);
|
|
1164
|
+
if (!await exists(reportPath)) {
|
|
1165
|
+
result.can_transition = false;
|
|
1166
|
+
result.missing_reports.push(seq.verdict.report_path);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
if (seq.verdict.conditions.length > 0) {
|
|
1170
|
+
for (const c of seq.verdict.conditions) {
|
|
1171
|
+
result.blocking_conditions.push(`${seq.trace.sequence_id}: condition "${c}"`);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
// Check unresolved L2/L3 feedback
|
|
1176
|
+
try {
|
|
1177
|
+
const state = await loadWorkflowState(projectRoot);
|
|
1178
|
+
if (state.pending_feedback?.length > 0) {
|
|
1179
|
+
const feedbackDir = path.join(projectRoot, '.timsquad', 'feedback');
|
|
1180
|
+
for (const fbId of state.pending_feedback) {
|
|
1181
|
+
const fbPath = path.join(feedbackDir, `${fbId}.json`);
|
|
1182
|
+
if (await exists(fbPath)) {
|
|
1183
|
+
const fb = await fs.readJson(fbPath);
|
|
1184
|
+
if (!fb.status || fb.status === 'open' || fb.status === 'in_review') {
|
|
1185
|
+
result.can_transition = false;
|
|
1186
|
+
result.blocking_conditions.push(`Unresolved L${fb.level} feedback: ${fbId} (${fb.trigger})`);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
catch { /* workflow state unavailable — skip feedback check */ }
|
|
1193
|
+
return result;
|
|
1194
|
+
}
|
|
1195
|
+
async function createSequenceLog(seqId, options) {
|
|
1196
|
+
const projectRoot = await findProjectRoot();
|
|
1197
|
+
if (!projectRoot)
|
|
1198
|
+
throw new Error('Not a TimSquad project');
|
|
1199
|
+
const entry = await buildSequenceLogData(projectRoot, seqId, options);
|
|
1200
|
+
const seqDir = getSequencesDir(projectRoot);
|
|
1201
|
+
await fs.ensureDir(seqDir);
|
|
1202
|
+
await fs.writeJson(path.join(seqDir, `${seqId}.json`), entry, { spaces: 2 });
|
|
1203
|
+
printSuccess(`Sequence log created: ${seqId}.json`);
|
|
1204
|
+
printKeyValue('Tasks', `${entry.tasks.success}/${entry.tasks.total} success`);
|
|
1205
|
+
printKeyValue('Verdict', entry.verdict.proceed ? 'PROCEED' : 'HOLD');
|
|
1206
|
+
}
|
|
1207
|
+
async function checkSequence(seqId) {
|
|
1208
|
+
const projectRoot = await findProjectRoot();
|
|
1209
|
+
if (!projectRoot)
|
|
1210
|
+
throw new Error('Not a TimSquad project');
|
|
1211
|
+
const taskLogs = await loadSequenceTaskLogs(projectRoot, seqId);
|
|
1212
|
+
printHeader(`Sequence Check: ${seqId}`);
|
|
1213
|
+
printKeyValue('Task Logs Found', String(taskLogs.length));
|
|
1214
|
+
if (taskLogs.length === 0) {
|
|
1215
|
+
console.log(colors.dim(' No task logs found for this sequence'));
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
let withSemantic = 0;
|
|
1219
|
+
for (const { file, data } of taskLogs) {
|
|
1220
|
+
const sem = hasSemantic(data.semantic) ? colors.success('✓ semantic') : colors.warning('○ no semantic');
|
|
1221
|
+
const status = data.status === 'completed' ? '✓' : '✗';
|
|
1222
|
+
console.log(` ${status} ${colors.agent(data.agent || '?')} ${sem} ${colors.dim(file)}`);
|
|
1223
|
+
if (hasSemantic(data.semantic))
|
|
1224
|
+
withSemantic++;
|
|
1225
|
+
}
|
|
1226
|
+
const coverage = Math.round((withSemantic / taskLogs.length) * 100);
|
|
1227
|
+
console.log('');
|
|
1228
|
+
printKeyValue('Semantic Coverage', `${coverage}% (${withSemantic}/${taskLogs.length})`);
|
|
1229
|
+
if (coverage < 100) {
|
|
1230
|
+
console.log(colors.warning('\n ⚠ Run tsq log enrich for tasks missing semantic data'));
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
// ============================================================
|
|
1234
|
+
// Step 5: tsq log phase (L3) + Gate
|
|
1235
|
+
// ============================================================
|
|
1236
|
+
async function listPhaseLogs() {
|
|
1237
|
+
const projectRoot = await findProjectRoot();
|
|
1238
|
+
if (!projectRoot)
|
|
1239
|
+
throw new Error('Not a TimSquad project');
|
|
1240
|
+
const phaseDir = getPhasesDir(projectRoot);
|
|
1241
|
+
if (!await exists(phaseDir)) {
|
|
1242
|
+
console.log(colors.dim('No phase logs found'));
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
const files = await listFiles('*.json', phaseDir);
|
|
1246
|
+
if (files.length === 0) {
|
|
1247
|
+
console.log(colors.dim('No phase logs found'));
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
printHeader(`Phase Logs (${files.length})`);
|
|
1251
|
+
for (const file of files.sort()) {
|
|
1252
|
+
try {
|
|
1253
|
+
const data = await fs.readJson(path.join(phaseDir, file));
|
|
1254
|
+
const phaseId = data.trace.phase_id;
|
|
1255
|
+
const status = data.execution.status;
|
|
1256
|
+
const seqs = `${data.sequences.completed}/${data.sequences.total} sequences`;
|
|
1257
|
+
console.log(` ${colors.agent(phaseId)} [${status}] ${seqs}`);
|
|
1258
|
+
}
|
|
1259
|
+
catch {
|
|
1260
|
+
console.log(` ${colors.dim(file)} (unreadable)`);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
async function viewPhaseLog(phaseId) {
|
|
1265
|
+
const projectRoot = await findProjectRoot();
|
|
1266
|
+
if (!projectRoot)
|
|
1267
|
+
throw new Error('Not a TimSquad project');
|
|
1268
|
+
const filePath = path.join(getPhasesDir(projectRoot), `${phaseId}.json`);
|
|
1269
|
+
if (!await exists(filePath)) {
|
|
1270
|
+
throw new Error(`Phase log not found: ${phaseId}`);
|
|
1271
|
+
}
|
|
1272
|
+
const data = await fs.readJson(filePath);
|
|
1273
|
+
printHeader(`Phase: ${phaseId}`);
|
|
1274
|
+
printKeyValue('Status', data.execution.status);
|
|
1275
|
+
printKeyValue('Duration', `${(data.execution.duration_ms / 1000 / 60).toFixed(1)} min`);
|
|
1276
|
+
printKeyValue('Sessions', String(data.execution.sessions_count));
|
|
1277
|
+
console.log(colors.subheader('\n Sequences'));
|
|
1278
|
+
printKeyValue(' Total', String(data.sequences.total));
|
|
1279
|
+
printKeyValue(' Completed', String(data.sequences.completed));
|
|
1280
|
+
printKeyValue(' Blocked', String(data.sequences.blocked));
|
|
1281
|
+
console.log(` IDs: ${data.sequences.ids.join(', ')}`);
|
|
1282
|
+
console.log(colors.subheader('\n Metrics'));
|
|
1283
|
+
printKeyValue(' Tasks', String(data.aggregate_metrics.total_tasks));
|
|
1284
|
+
printKeyValue(' Success Rate', `${(data.aggregate_metrics.task_success_rate * 100).toFixed(0)}%`);
|
|
1285
|
+
printKeyValue(' Rework Rate', `${(data.aggregate_metrics.task_rework_rate * 100).toFixed(0)}%`);
|
|
1286
|
+
printKeyValue(' Files Changed', String(data.aggregate_metrics.total_files_changed));
|
|
1287
|
+
printKeyValue(' SSOT Conformance', `${(data.aggregate_metrics.ssot_conformance_rate * 100).toFixed(0)}%`);
|
|
1288
|
+
console.log(colors.subheader('\n DORA'));
|
|
1289
|
+
printKeyValue(' Lead Time', `${(data.dora_derived.lead_time_ms / 1000 / 60).toFixed(1)} min`);
|
|
1290
|
+
printKeyValue(' Change Failure Rate', `${(data.dora_derived.change_failure_rate * 100).toFixed(0)}%`);
|
|
1291
|
+
if (data.retrospective.keep.length || data.retrospective.problem.length || data.retrospective.try.length) {
|
|
1292
|
+
console.log(colors.subheader('\n Retrospective'));
|
|
1293
|
+
if (data.retrospective.keep.length) {
|
|
1294
|
+
console.log(' Keep:');
|
|
1295
|
+
data.retrospective.keep.forEach(k => console.log(` + ${k}`));
|
|
1296
|
+
}
|
|
1297
|
+
if (data.retrospective.problem.length) {
|
|
1298
|
+
console.log(' Problem:');
|
|
1299
|
+
data.retrospective.problem.forEach(p => console.log(` - ${p}`));
|
|
1300
|
+
}
|
|
1301
|
+
if (data.retrospective.try.length) {
|
|
1302
|
+
console.log(' Try:');
|
|
1303
|
+
data.retrospective.try.forEach(t => console.log(` → ${t}`));
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
async function createPhaseLog(phaseId, options) {
|
|
1308
|
+
const projectRoot = await findProjectRoot();
|
|
1309
|
+
if (!projectRoot)
|
|
1310
|
+
throw new Error('Not a TimSquad project');
|
|
1311
|
+
const seqIds = options.sequences.split(',').map(s => s.trim());
|
|
1312
|
+
const seqDir = getSequencesDir(projectRoot);
|
|
1313
|
+
const now = getTimestamp();
|
|
1314
|
+
// Load sequence logs
|
|
1315
|
+
const seqLogs = [];
|
|
1316
|
+
let completed = 0, blocked = 0;
|
|
1317
|
+
for (const id of seqIds) {
|
|
1318
|
+
const seqPath = path.join(seqDir, `${id}.json`);
|
|
1319
|
+
if (await exists(seqPath)) {
|
|
1320
|
+
const data = await fs.readJson(seqPath);
|
|
1321
|
+
seqLogs.push(data);
|
|
1322
|
+
if (data.execution.status === 'completed')
|
|
1323
|
+
completed++;
|
|
1324
|
+
else
|
|
1325
|
+
blocked++;
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
blocked++;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
// Aggregate
|
|
1332
|
+
let totalTasks = 0, totalSuccess = 0, totalRework = 0, totalFiles = 0;
|
|
1333
|
+
let totalIssues1 = 0, totalIssues2 = 0, totalIssues3 = 0;
|
|
1334
|
+
const durations = [];
|
|
1335
|
+
for (const seq of seqLogs) {
|
|
1336
|
+
totalTasks += seq.tasks.total;
|
|
1337
|
+
totalSuccess += seq.tasks.success;
|
|
1338
|
+
totalRework += seq.tasks.rework;
|
|
1339
|
+
durations.push(seq.execution.duration_ms);
|
|
1340
|
+
// Count issues from analysis axes
|
|
1341
|
+
for (const axis of Object.values(seq.analysis)) {
|
|
1342
|
+
const r = axis;
|
|
1343
|
+
for (const issue of r.issues) {
|
|
1344
|
+
if (issue.level === 1)
|
|
1345
|
+
totalIssues1++;
|
|
1346
|
+
else if (issue.level === 2)
|
|
1347
|
+
totalIssues2++;
|
|
1348
|
+
else
|
|
1349
|
+
totalIssues3++;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
// Collect total files from task logs
|
|
1354
|
+
for (const seqId of seqIds) {
|
|
1355
|
+
const taskLogs = await loadSequenceTaskLogs(projectRoot, seqId);
|
|
1356
|
+
for (const { data } of taskLogs) {
|
|
1357
|
+
totalFiles += data.mechanical?.files?.length || 0;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
const timestamps = seqLogs
|
|
1361
|
+
.map(s => [new Date(s.execution.started_at).getTime(), new Date(s.execution.completed_at).getTime()])
|
|
1362
|
+
.flat()
|
|
1363
|
+
.filter(t => t > 0);
|
|
1364
|
+
const leadTime = timestamps.length >= 2 ? Math.max(...timestamps) - Math.min(...timestamps) : 0;
|
|
1365
|
+
const entry = {
|
|
1366
|
+
schema_version: LOG_SCHEMA_VERSION,
|
|
1367
|
+
trace: { phase_id: phaseId },
|
|
1368
|
+
execution: {
|
|
1369
|
+
status: blocked > 0 ? 'aborted' : 'completed',
|
|
1370
|
+
started_at: seqLogs[0]?.execution.started_at || now,
|
|
1371
|
+
completed_at: now,
|
|
1372
|
+
duration_ms: leadTime,
|
|
1373
|
+
sessions_count: seqLogs.length,
|
|
1374
|
+
},
|
|
1375
|
+
sequences: { total: seqIds.length, completed, blocked, ids: seqIds },
|
|
1376
|
+
aggregate_metrics: {
|
|
1377
|
+
total_tasks: totalTasks,
|
|
1378
|
+
task_success_rate: totalTasks > 0 ? totalSuccess / totalTasks : 0,
|
|
1379
|
+
task_rework_rate: totalTasks > 0 ? totalRework / totalTasks : 0,
|
|
1380
|
+
total_files_changed: totalFiles,
|
|
1381
|
+
total_issues: { level_1: totalIssues1, level_2: totalIssues2, level_3: totalIssues3 },
|
|
1382
|
+
ssot_conformance_rate: 0, // Placeholder — from architect reports
|
|
1383
|
+
mean_sequence_duration_ms: durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : 0,
|
|
1384
|
+
},
|
|
1385
|
+
dora_derived: {
|
|
1386
|
+
lead_time_ms: leadTime,
|
|
1387
|
+
change_failure_rate: totalTasks > 0 ? (totalTasks - totalSuccess) / totalTasks : 0,
|
|
1388
|
+
mean_recovery_time_ms: null,
|
|
1389
|
+
},
|
|
1390
|
+
planning: {
|
|
1391
|
+
original_sequences: seqIds,
|
|
1392
|
+
added_sequences: [],
|
|
1393
|
+
removed_sequences: [],
|
|
1394
|
+
scope_changes: [],
|
|
1395
|
+
plan_adherence_rate: 1,
|
|
1396
|
+
},
|
|
1397
|
+
retrospective: {
|
|
1398
|
+
keep: options.retroKeep ? options.retroKeep.split(',').map(s => s.trim()) : [],
|
|
1399
|
+
problem: options.retroProblem ? options.retroProblem.split(',').map(s => s.trim()) : [],
|
|
1400
|
+
try: options.retroTry ? options.retroTry.split(',').map(s => s.trim()) : [],
|
|
1401
|
+
},
|
|
1402
|
+
knowledge_extracted: [],
|
|
1403
|
+
};
|
|
1404
|
+
const phaseDir = getPhasesDir(projectRoot);
|
|
1405
|
+
await fs.ensureDir(phaseDir);
|
|
1406
|
+
await fs.writeJson(path.join(phaseDir, `${phaseId}.json`), entry, { spaces: 2 });
|
|
1407
|
+
printSuccess(`Phase log created: ${phaseId}.json`);
|
|
1408
|
+
printKeyValue('Sequences', `${completed}/${seqIds.length} completed`);
|
|
1409
|
+
printKeyValue('Tasks', `${totalSuccess}/${totalTasks} success`);
|
|
1410
|
+
}
|
|
1411
|
+
async function checkPhaseGate(phaseId) {
|
|
1412
|
+
const projectRoot = await findProjectRoot();
|
|
1413
|
+
if (!projectRoot)
|
|
1414
|
+
throw new Error('Not a TimSquad project');
|
|
1415
|
+
const result = await buildPhaseGateData(projectRoot, phaseId);
|
|
1416
|
+
printHeader(`Phase Gate: ${phaseId}`);
|
|
1417
|
+
if (result.can_transition) {
|
|
1418
|
+
console.log(colors.success('\n ✓ PASSED — Phase transition allowed'));
|
|
1419
|
+
}
|
|
1420
|
+
else {
|
|
1421
|
+
console.log(colors.error('\n ✗ BLOCKED — Phase transition denied'));
|
|
1422
|
+
if (result.missing_reports.length > 0) {
|
|
1423
|
+
console.log(colors.subheader('\n Missing Reports'));
|
|
1424
|
+
for (const r of result.missing_reports) {
|
|
1425
|
+
console.log(` - ${r}`);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
if (result.blocking_conditions.length > 0) {
|
|
1429
|
+
console.log(colors.subheader('\n Blocking Conditions'));
|
|
1430
|
+
for (const c of result.blocking_conditions) {
|
|
1431
|
+
console.log(` - ${c}`);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
271
1436
|
//# sourceMappingURL=log.js.map
|