@su-record/vibe 3.2.13 → 3.2.15
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.md +15 -14
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/dist/cli/setup/ProjectSetup.d.ts +6 -0
- package/dist/cli/setup/ProjectSetup.d.ts.map +1 -1
- package/dist/cli/setup/ProjectSetup.js +19 -13
- package/dist/cli/setup/ProjectSetup.js.map +1 -1
- package/dist/infra/lib/codex-proxy.d.ts +126 -0
- package/dist/infra/lib/codex-proxy.d.ts.map +1 -1
- package/dist/infra/lib/codex-proxy.js +16 -16
- package/dist/infra/lib/codex-proxy.js.map +1 -1
- package/dist/infra/lib/figma/audit.d.ts.map +1 -1
- package/dist/infra/lib/figma/audit.js +0 -2
- package/dist/infra/lib/figma/audit.js.map +1 -1
- package/dist/infra/lib/figma/extract.d.ts +6 -1
- package/dist/infra/lib/figma/extract.d.ts.map +1 -1
- package/dist/infra/lib/figma/extract.js +17 -15
- package/dist/infra/lib/figma/extract.js.map +1 -1
- package/dist/infra/lib/figma/types.d.ts +103 -0
- package/dist/infra/lib/figma/types.d.ts.map +1 -1
- package/dist/infra/lib/figma/types.js +8 -0
- package/dist/infra/lib/figma/types.js.map +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/scripts/clone-extract.js +16 -1
- package/hooks/scripts/code-check.js +72 -55
- package/hooks/scripts/lib/anchor.js +74 -0
- package/hooks/scripts/lib/console-allow.js +66 -0
- package/hooks/scripts/lib/dispatcher.js +71 -19
- package/hooks/scripts/lib/inbox.js +60 -0
- package/hooks/scripts/loop-ledger.js +24 -1
- package/hooks/scripts/post-edit-dispatcher.js +4 -3
- package/hooks/scripts/post-edit.js +2 -2
- package/package.json +13 -9
- package/skills/vibe/SKILL.md +2 -1
- package/skills/vibe.clone/references/verification-loops.md +6 -3
- package/skills/vibe.loop/SKILL.md +7 -4
- package/skills/vibe.review/SKILL.md +5 -1
- package/skills/vibe.run/references/e2e-and-autofix.md +1 -1
- package/skills/vibe.run/references/process-steps.md +1 -1
- package/vibe/constitution.md +1 -1
- package/vibe/rules/loop-contract.md +40 -3
- package/vibe/rules/quality/checklist.md +3 -3
- package/vibe/templates/constitution-template.md +1 -1
- package/dist/__tests__/agent-model-sync.test.d.ts +0 -2
- package/dist/__tests__/agent-model-sync.test.d.ts.map +0 -1
- package/dist/__tests__/agent-model-sync.test.js +0 -71
- package/dist/__tests__/agent-model-sync.test.js.map +0 -1
- package/dist/__tests__/architecture.test.d.ts +0 -2
- package/dist/__tests__/architecture.test.d.ts.map +0 -1
- package/dist/__tests__/architecture.test.js +0 -207
- package/dist/__tests__/architecture.test.js.map +0 -1
- package/dist/__tests__/skill-namespace.test.d.ts +0 -2
- package/dist/__tests__/skill-namespace.test.d.ts.map +0 -1
- package/dist/__tests__/skill-namespace.test.js +0 -60
- package/dist/__tests__/skill-namespace.test.js.map +0 -1
- package/dist/__tests__/stakes-contract.test.d.ts +0 -2
- package/dist/__tests__/stakes-contract.test.d.ts.map +0 -1
- package/dist/__tests__/stakes-contract.test.js +0 -166
- package/dist/__tests__/stakes-contract.test.js.map +0 -1
- package/dist/__tests__/stuck-semantics.test.d.ts +0 -2
- package/dist/__tests__/stuck-semantics.test.d.ts.map +0 -1
- package/dist/__tests__/stuck-semantics.test.js +0 -109
- package/dist/__tests__/stuck-semantics.test.js.map +0 -1
- package/dist/__tests__/wiring-integrity.test.d.ts +0 -2
- package/dist/__tests__/wiring-integrity.test.d.ts.map +0 -1
- package/dist/__tests__/wiring-integrity.test.js +0 -160
- package/dist/__tests__/wiring-integrity.test.js.map +0 -1
- package/dist/cli/commands/info.test.d.ts +0 -2
- package/dist/cli/commands/info.test.d.ts.map +0 -1
- package/dist/cli/commands/info.test.js +0 -74
- package/dist/cli/commands/info.test.js.map +0 -1
- package/dist/cli/commands/upgrade.test.d.ts +0 -2
- package/dist/cli/commands/upgrade.test.d.ts.map +0 -1
- package/dist/cli/commands/upgrade.test.js +0 -99
- package/dist/cli/commands/upgrade.test.js.map +0 -1
- package/dist/cli/design/design-md-parser.test.d.ts +0 -2
- package/dist/cli/design/design-md-parser.test.d.ts.map +0 -1
- package/dist/cli/design/design-md-parser.test.js +0 -149
- package/dist/cli/design/design-md-parser.test.js.map +0 -1
- package/dist/cli/detect.characterization.test.d.ts +0 -7
- package/dist/cli/detect.characterization.test.d.ts.map +0 -1
- package/dist/cli/detect.characterization.test.js +0 -294
- package/dist/cli/detect.characterization.test.js.map +0 -1
- package/dist/cli/postinstall/fs-utils.test.d.ts +0 -2
- package/dist/cli/postinstall/fs-utils.test.d.ts.map +0 -1
- package/dist/cli/postinstall/fs-utils.test.js +0 -259
- package/dist/cli/postinstall/fs-utils.test.js.map +0 -1
- package/dist/cli/setup/CodexHooks.test.d.ts +0 -2
- package/dist/cli/setup/CodexHooks.test.d.ts.map +0 -1
- package/dist/cli/setup/CodexHooks.test.js +0 -52
- package/dist/cli/setup/CodexHooks.test.js.map +0 -1
- package/dist/cli/setup/GlobalInstaller.test.d.ts +0 -2
- package/dist/cli/setup/GlobalInstaller.test.d.ts.map +0 -1
- package/dist/cli/setup/GlobalInstaller.test.js +0 -16
- package/dist/cli/setup/GlobalInstaller.test.js.map +0 -1
- package/dist/cli/utils/cli-detector.test.d.ts +0 -2
- package/dist/cli/utils/cli-detector.test.d.ts.map +0 -1
- package/dist/cli/utils/cli-detector.test.js +0 -52
- package/dist/cli/utils/cli-detector.test.js.map +0 -1
- package/dist/infra/lib/SkillFrontmatter.test.d.ts +0 -2
- package/dist/infra/lib/SkillFrontmatter.test.d.ts.map +0 -1
- package/dist/infra/lib/SkillFrontmatter.test.js +0 -60
- package/dist/infra/lib/SkillFrontmatter.test.js.map +0 -1
- package/dist/infra/lib/__tests__/AutomationLevel.test.d.ts +0 -2
- package/dist/infra/lib/__tests__/AutomationLevel.test.d.ts.map +0 -1
- package/dist/infra/lib/__tests__/AutomationLevel.test.js +0 -297
- package/dist/infra/lib/__tests__/AutomationLevel.test.js.map +0 -1
- package/dist/infra/lib/__tests__/ComponentRegistry.test.d.ts +0 -2
- package/dist/infra/lib/__tests__/ComponentRegistry.test.d.ts.map +0 -1
- package/dist/infra/lib/__tests__/ComponentRegistry.test.js +0 -99
- package/dist/infra/lib/__tests__/ComponentRegistry.test.js.map +0 -1
- package/dist/infra/lib/__tests__/DecisionTracer.test.d.ts +0 -2
- package/dist/infra/lib/__tests__/DecisionTracer.test.d.ts.map +0 -1
- package/dist/infra/lib/__tests__/DecisionTracer.test.js +0 -274
- package/dist/infra/lib/__tests__/DecisionTracer.test.js.map +0 -1
- package/dist/infra/lib/__tests__/InteractiveCheckpoint.test.d.ts +0 -2
- package/dist/infra/lib/__tests__/InteractiveCheckpoint.test.d.ts.map +0 -1
- package/dist/infra/lib/__tests__/InteractiveCheckpoint.test.js +0 -350
- package/dist/infra/lib/__tests__/InteractiveCheckpoint.test.js.map +0 -1
- package/dist/infra/lib/__tests__/LoopBreaker.test.d.ts +0 -2
- package/dist/infra/lib/__tests__/LoopBreaker.test.d.ts.map +0 -1
- package/dist/infra/lib/__tests__/LoopBreaker.test.js +0 -340
- package/dist/infra/lib/__tests__/LoopBreaker.test.js.map +0 -1
- package/dist/infra/lib/__tests__/VerificationLoop.test.d.ts +0 -2
- package/dist/infra/lib/__tests__/VerificationLoop.test.d.ts.map +0 -1
- package/dist/infra/lib/__tests__/VerificationLoop.test.js +0 -486
- package/dist/infra/lib/__tests__/VerificationLoop.test.js.map +0 -1
- package/dist/infra/lib/config/GlobalConfigManager.test.d.ts +0 -2
- package/dist/infra/lib/config/GlobalConfigManager.test.d.ts.map +0 -1
- package/dist/infra/lib/config/GlobalConfigManager.test.js +0 -9
- package/dist/infra/lib/config/GlobalConfigManager.test.js.map +0 -1
- package/dist/infra/lib/embedding/__tests__/EmbeddingProvider.test.d.ts +0 -2
- package/dist/infra/lib/embedding/__tests__/EmbeddingProvider.test.d.ts.map +0 -1
- package/dist/infra/lib/embedding/__tests__/EmbeddingProvider.test.js +0 -43
- package/dist/infra/lib/embedding/__tests__/EmbeddingProvider.test.js.map +0 -1
- package/dist/infra/lib/embedding/__tests__/VectorStore.test.d.ts +0 -2
- package/dist/infra/lib/embedding/__tests__/VectorStore.test.d.ts.map +0 -1
- package/dist/infra/lib/embedding/__tests__/VectorStore.test.js +0 -94
- package/dist/infra/lib/embedding/__tests__/VectorStore.test.js.map +0 -1
- package/dist/infra/lib/embedding/__tests__/cosine.test.d.ts +0 -2
- package/dist/infra/lib/embedding/__tests__/cosine.test.d.ts.map +0 -1
- package/dist/infra/lib/embedding/__tests__/cosine.test.js +0 -73
- package/dist/infra/lib/embedding/__tests__/cosine.test.js.map +0 -1
- package/dist/infra/lib/evolution/__tests__/deprecation.test.d.ts +0 -2
- package/dist/infra/lib/evolution/__tests__/deprecation.test.d.ts.map +0 -1
- package/dist/infra/lib/evolution/__tests__/deprecation.test.js +0 -251
- package/dist/infra/lib/evolution/__tests__/deprecation.test.js.map +0 -1
- package/dist/infra/lib/evolution/__tests__/eval.test.d.ts +0 -2
- package/dist/infra/lib/evolution/__tests__/eval.test.d.ts.map +0 -1
- package/dist/infra/lib/evolution/__tests__/eval.test.js +0 -603
- package/dist/infra/lib/evolution/__tests__/eval.test.js.map +0 -1
- package/dist/infra/lib/evolution/__tests__/generation.test.d.ts +0 -2
- package/dist/infra/lib/evolution/__tests__/generation.test.d.ts.map +0 -1
- package/dist/infra/lib/evolution/__tests__/generation.test.js +0 -396
- package/dist/infra/lib/evolution/__tests__/generation.test.js.map +0 -1
- package/dist/infra/lib/evolution/__tests__/insight.test.d.ts +0 -2
- package/dist/infra/lib/evolution/__tests__/insight.test.d.ts.map +0 -1
- package/dist/infra/lib/evolution/__tests__/insight.test.js +0 -204
- package/dist/infra/lib/evolution/__tests__/insight.test.js.map +0 -1
- package/dist/infra/lib/evolution/__tests__/integration.test.d.ts +0 -2
- package/dist/infra/lib/evolution/__tests__/integration.test.d.ts.map +0 -1
- package/dist/infra/lib/evolution/__tests__/integration.test.js +0 -206
- package/dist/infra/lib/evolution/__tests__/integration.test.js.map +0 -1
- package/dist/infra/lib/evolution/__tests__/lifecycle.test.d.ts +0 -2
- package/dist/infra/lib/evolution/__tests__/lifecycle.test.d.ts.map +0 -1
- package/dist/infra/lib/evolution/__tests__/lifecycle.test.js +0 -350
- package/dist/infra/lib/evolution/__tests__/lifecycle.test.js.map +0 -1
- package/dist/infra/lib/evolution/__tests__/parity.test.d.ts +0 -2
- package/dist/infra/lib/evolution/__tests__/parity.test.d.ts.map +0 -1
- package/dist/infra/lib/evolution/__tests__/parity.test.js +0 -319
- package/dist/infra/lib/evolution/__tests__/parity.test.js.map +0 -1
- package/dist/infra/lib/llm/auth/AuthProfileManager.test.d.ts +0 -5
- package/dist/infra/lib/llm/auth/AuthProfileManager.test.d.ts.map +0 -1
- package/dist/infra/lib/llm/auth/AuthProfileManager.test.js +0 -168
- package/dist/infra/lib/llm/auth/AuthProfileManager.test.js.map +0 -1
- package/dist/infra/lib/llm/auth/TokenRefresher.test.d.ts +0 -2
- package/dist/infra/lib/llm/auth/TokenRefresher.test.d.ts.map +0 -1
- package/dist/infra/lib/llm/auth/TokenRefresher.test.js +0 -120
- package/dist/infra/lib/llm/auth/TokenRefresher.test.js.map +0 -1
- package/dist/infra/lib/llm/timeout.test.d.ts +0 -2
- package/dist/infra/lib/llm/timeout.test.d.ts.map +0 -1
- package/dist/infra/lib/llm/timeout.test.js +0 -50
- package/dist/infra/lib/llm/timeout.test.js.map +0 -1
- package/dist/infra/lib/llm-availability.test.d.ts +0 -2
- package/dist/infra/lib/llm-availability.test.d.ts.map +0 -1
- package/dist/infra/lib/llm-availability.test.js +0 -37
- package/dist/infra/lib/llm-availability.test.js.map +0 -1
- package/dist/infra/lib/memory/KnowledgeGraph.test.d.ts +0 -2
- package/dist/infra/lib/memory/KnowledgeGraph.test.d.ts.map +0 -1
- package/dist/infra/lib/memory/KnowledgeGraph.test.js +0 -189
- package/dist/infra/lib/memory/KnowledgeGraph.test.js.map +0 -1
- package/dist/infra/lib/memory/MemorySearch.test.d.ts +0 -2
- package/dist/infra/lib/memory/MemorySearch.test.d.ts.map +0 -1
- package/dist/infra/lib/memory/MemorySearch.test.js +0 -149
- package/dist/infra/lib/memory/MemorySearch.test.js.map +0 -1
- package/dist/infra/lib/memory/MemoryStorage.test.d.ts +0 -2
- package/dist/infra/lib/memory/MemoryStorage.test.d.ts.map +0 -1
- package/dist/infra/lib/memory/MemoryStorage.test.js +0 -198
- package/dist/infra/lib/memory/MemoryStorage.test.js.map +0 -1
- package/dist/infra/lib/memory/SessionRAGRetriever.test.d.ts +0 -2
- package/dist/infra/lib/memory/SessionRAGRetriever.test.d.ts.map +0 -1
- package/dist/infra/lib/memory/SessionRAGRetriever.test.js +0 -180
- package/dist/infra/lib/memory/SessionRAGRetriever.test.js.map +0 -1
- package/dist/infra/lib/memory/SessionRAGStore.test.d.ts +0 -2
- package/dist/infra/lib/memory/SessionRAGStore.test.d.ts.map +0 -1
- package/dist/infra/lib/memory/SessionRAGStore.test.js +0 -326
- package/dist/infra/lib/memory/SessionRAGStore.test.js.map +0 -1
- package/dist/infra/lib/memory/__tests__/InMemoryStorage.test.d.ts +0 -2
- package/dist/infra/lib/memory/__tests__/InMemoryStorage.test.d.ts.map +0 -1
- package/dist/infra/lib/memory/__tests__/InMemoryStorage.test.js +0 -114
- package/dist/infra/lib/memory/__tests__/InMemoryStorage.test.js.map +0 -1
- package/dist/infra/lib/memory/__tests__/ReflectionStore.test.d.ts +0 -2
- package/dist/infra/lib/memory/__tests__/ReflectionStore.test.d.ts.map +0 -1
- package/dist/infra/lib/memory/__tests__/ReflectionStore.test.js +0 -196
- package/dist/infra/lib/memory/__tests__/ReflectionStore.test.js.map +0 -1
- package/dist/infra/lib/memory/index.test.d.ts +0 -2
- package/dist/infra/lib/memory/index.test.d.ts.map +0 -1
- package/dist/infra/lib/memory/index.test.js +0 -23
- package/dist/infra/lib/memory/index.test.js.map +0 -1
- package/dist/infra/lib/telemetry/SkillTelemetry.test.d.ts +0 -2
- package/dist/infra/lib/telemetry/SkillTelemetry.test.d.ts.map +0 -1
- package/dist/infra/lib/telemetry/SkillTelemetry.test.js +0 -124
- package/dist/infra/lib/telemetry/SkillTelemetry.test.js.map +0 -1
- package/dist/infra/lib/ui-ux/Bm25Engine.test.d.ts +0 -2
- package/dist/infra/lib/ui-ux/Bm25Engine.test.d.ts.map +0 -1
- package/dist/infra/lib/ui-ux/Bm25Engine.test.js +0 -159
- package/dist/infra/lib/ui-ux/Bm25Engine.test.js.map +0 -1
- package/dist/infra/lib/ui-ux/CsvDataLoader.test.d.ts +0 -2
- package/dist/infra/lib/ui-ux/CsvDataLoader.test.d.ts.map +0 -1
- package/dist/infra/lib/ui-ux/CsvDataLoader.test.js +0 -141
- package/dist/infra/lib/ui-ux/CsvDataLoader.test.js.map +0 -1
- package/dist/infra/lib/ui-ux/DesignSystemGenerator.test.d.ts +0 -2
- package/dist/infra/lib/ui-ux/DesignSystemGenerator.test.d.ts.map +0 -1
- package/dist/infra/lib/ui-ux/DesignSystemGenerator.test.js +0 -370
- package/dist/infra/lib/ui-ux/DesignSystemGenerator.test.js.map +0 -1
- package/dist/infra/lib/ui-ux/SearchService.test.d.ts +0 -2
- package/dist/infra/lib/ui-ux/SearchService.test.d.ts.map +0 -1
- package/dist/infra/lib/ui-ux/SearchService.test.js +0 -215
- package/dist/infra/lib/ui-ux/SearchService.test.js.map +0 -1
- package/dist/test-helpers/index.d.ts +0 -36
- package/dist/test-helpers/index.d.ts.map +0 -1
- package/dist/test-helpers/index.js +0 -85
- package/dist/test-helpers/index.js.map +0 -1
- package/dist/test-helpers/index.test.d.ts +0 -2
- package/dist/test-helpers/index.test.d.ts.map +0 -1
- package/dist/test-helpers/index.test.js +0 -92
- package/dist/test-helpers/index.test.js.map +0 -1
- package/dist/tests/curated-release-notes.test.d.ts +0 -2
- package/dist/tests/curated-release-notes.test.d.ts.map +0 -1
- package/dist/tests/curated-release-notes.test.js +0 -160
- package/dist/tests/curated-release-notes.test.js.map +0 -1
- package/dist/tools/convention/analyzeComplexity.test.d.ts +0 -2
- package/dist/tools/convention/analyzeComplexity.test.d.ts.map +0 -1
- package/dist/tools/convention/analyzeComplexity.test.js +0 -207
- package/dist/tools/convention/analyzeComplexity.test.js.map +0 -1
- package/dist/tools/convention/validateCodeQuality.test.d.ts +0 -2
- package/dist/tools/convention/validateCodeQuality.test.d.ts.map +0 -1
- package/dist/tools/convention/validateCodeQuality.test.js +0 -230
- package/dist/tools/convention/validateCodeQuality.test.js.map +0 -1
- package/dist/tools/loop/validateLoopDefinition.test.d.ts +0 -14
- package/dist/tools/loop/validateLoopDefinition.test.d.ts.map +0 -1
- package/dist/tools/loop/validateLoopDefinition.test.js +0 -229
- package/dist/tools/loop/validateLoopDefinition.test.js.map +0 -1
- package/dist/tools/spec/executionPacket.test.d.ts +0 -2
- package/dist/tools/spec/executionPacket.test.d.ts.map +0 -1
- package/dist/tools/spec/executionPacket.test.js +0 -374
- package/dist/tools/spec/executionPacket.test.js.map +0 -1
- package/dist/tools/spec/prdParser.test.d.ts +0 -6
- package/dist/tools/spec/prdParser.test.d.ts.map +0 -1
- package/dist/tools/spec/prdParser.test.js +0 -332
- package/dist/tools/spec/prdParser.test.js.map +0 -1
- package/dist/tools/spec/specGenerator.test.d.ts +0 -2
- package/dist/tools/spec/specGenerator.test.d.ts.map +0 -1
- package/dist/tools/spec/specGenerator.test.js +0 -130
- package/dist/tools/spec/specGenerator.test.js.map +0 -1
- package/dist/tools/spec/traceabilityMatrix.path-resolution.test.d.ts +0 -10
- package/dist/tools/spec/traceabilityMatrix.path-resolution.test.d.ts.map +0 -1
- package/dist/tools/spec/traceabilityMatrix.path-resolution.test.js +0 -120
- package/dist/tools/spec/traceabilityMatrix.path-resolution.test.js.map +0 -1
- package/dist/tools/spec/traceabilityMatrix.test.d.ts +0 -6
- package/dist/tools/spec/traceabilityMatrix.test.d.ts.map +0 -1
- package/dist/tools/spec/traceabilityMatrix.test.js +0 -359
- package/dist/tools/spec/traceabilityMatrix.test.js.map +0 -1
- package/hooks/scripts/__tests__/.vibe/command-log.txt +0 -3
- package/hooks/scripts/__tests__/auto-test-debounce.test.js +0 -145
- package/hooks/scripts/__tests__/clone-behaviors.test.js +0 -152
- package/hooks/scripts/__tests__/clone-merge-responsive.test.js +0 -106
- package/hooks/scripts/__tests__/code-check-detectors.test.js +0 -213
- package/hooks/scripts/__tests__/code-check-memory-write.test.js +0 -81
- package/hooks/scripts/__tests__/codex-hook-adapter.test.js +0 -64
- package/hooks/scripts/__tests__/curation-index.test.js +0 -157
- package/hooks/scripts/__tests__/dispatcher-inprocess.test.js +0 -99
- package/hooks/scripts/__tests__/figma-extract.test.js +0 -338
- package/hooks/scripts/__tests__/hook-payload.test.js +0 -29
- package/hooks/scripts/__tests__/llm-orchestrate-antigravity.test.js +0 -66
- package/hooks/scripts/__tests__/loop-ledger.test.js +0 -321
- package/hooks/scripts/__tests__/post-edit-dispatcher.test.js +0 -139
- package/hooks/scripts/__tests__/pre-tool-guard.test.js +0 -523
- package/hooks/scripts/__tests__/recipe-extractor.test.js +0 -244
- package/hooks/scripts/__tests__/run-ledger-verify-required.test.js +0 -151
- package/hooks/scripts/__tests__/run-ledger.test.js +0 -481
- package/hooks/scripts/__tests__/scope-from-spec.test.js +0 -215
- package/hooks/scripts/__tests__/sentinel-guard.test.js +0 -263
- package/hooks/scripts/__tests__/step-counter.test.js +0 -459
- package/hooks/scripts/__tests__/utils-npm-root.test.js +0 -99
|
@@ -10,11 +10,10 @@
|
|
|
10
10
|
* (run-ledger verifyRequired)를 태우지 않는다 — 그 게이트는 결정론적
|
|
11
11
|
* 검증 흐름(vibe.verify) 전용이다.
|
|
12
12
|
*/
|
|
13
|
-
import { getToolsBaseUrl, PROJECT_DIR
|
|
13
|
+
import { getToolsBaseUrl, PROJECT_DIR } from './utils.js';
|
|
14
14
|
import { readFileSync } from 'fs';
|
|
15
|
-
import path from 'path';
|
|
16
15
|
import { buildCliCtx, isDirectRun } from './lib/hook-context.js';
|
|
17
|
-
import {
|
|
16
|
+
import { CODE_EXT_RE, shouldCheckConsole } from './lib/console-allow.js';
|
|
18
17
|
|
|
19
18
|
const BASE_URL = getToolsBaseUrl();
|
|
20
19
|
|
|
@@ -31,50 +30,6 @@ const P1_DETECTORS = [
|
|
|
31
30
|
];
|
|
32
31
|
|
|
33
32
|
const TS_EXT_RE = /\.(ts|tsx)$/;
|
|
34
|
-
const CODE_EXT_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
35
|
-
|
|
36
|
-
// console.log 기본 허용 경로 (glob 패턴 → 정규식으로 변환)
|
|
37
|
-
const DEFAULT_CONSOLE_ALLOW_GLOBS = [
|
|
38
|
-
'hooks/scripts/**',
|
|
39
|
-
'scripts/**',
|
|
40
|
-
'**/cli/**',
|
|
41
|
-
'**/*.test.*',
|
|
42
|
-
'**/*.spec.*',
|
|
43
|
-
'**/__tests__/**',
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* .vibe/config.json의 qualityCheck.consoleAllow 글로브 목록 로드.
|
|
48
|
-
* 기본 글로브와 병합하여 반환.
|
|
49
|
-
* @returns {RegExp[]}
|
|
50
|
-
*/
|
|
51
|
-
function loadConsoleAllowPatterns() {
|
|
52
|
-
try {
|
|
53
|
-
const cfg = readProjectConfig();
|
|
54
|
-
const extra = cfg?.qualityCheck?.consoleAllow;
|
|
55
|
-
const globs = Array.isArray(extra)
|
|
56
|
-
? [...DEFAULT_CONSOLE_ALLOW_GLOBS, ...extra]
|
|
57
|
-
: DEFAULT_CONSOLE_ALLOW_GLOBS;
|
|
58
|
-
return globs.map(g => globToRegExp(g));
|
|
59
|
-
} catch {
|
|
60
|
-
return DEFAULT_CONSOLE_ALLOW_GLOBS.map(g => globToRegExp(g));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* 파일 경로가 console.log 허용 경로인지 판단.
|
|
66
|
-
* @param {string} filePath - 절대 또는 프로젝트 상대 경로
|
|
67
|
-
* @returns {boolean}
|
|
68
|
-
*/
|
|
69
|
-
function isConsoleAllowed(filePath) {
|
|
70
|
-
try {
|
|
71
|
-
const rel = path.relative(PROJECT_DIR, path.resolve(filePath)).replace(/\\/g, '/');
|
|
72
|
-
const patterns = loadConsoleAllowPatterns();
|
|
73
|
-
return patterns.some(re => re.test(rel));
|
|
74
|
-
} catch {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
33
|
|
|
79
34
|
/**
|
|
80
35
|
* hook ctx에서 수정된 파일 경로 추출.
|
|
@@ -99,16 +54,72 @@ function classifyObservation(files) {
|
|
|
99
54
|
return { type: 'feature', title: 'Code modified' };
|
|
100
55
|
}
|
|
101
56
|
|
|
57
|
+
/**
|
|
58
|
+
* 백슬래시로 이스케이프되지 않은 첫 token 위치. 없으면 -1.
|
|
59
|
+
* 템플릿 리터럴 안의 \` 를 종료 백틱으로 오인하지 않기 위해 필요하다.
|
|
60
|
+
* @param {string} str
|
|
61
|
+
* @param {string} token
|
|
62
|
+
* @returns {number}
|
|
63
|
+
*/
|
|
64
|
+
function findUnescaped(str, token) {
|
|
65
|
+
for (let i = 0; i < str.length; i++) {
|
|
66
|
+
if (str[i] === '\\') { i++; continue; }
|
|
67
|
+
if (str.startsWith(token, i)) return i;
|
|
68
|
+
}
|
|
69
|
+
return -1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 한 줄에서 코드가 아닌 구간(주석·문자열·템플릿 리터럴)을 지운다.
|
|
74
|
+
* 여러 줄에 걸친 블록 주석·템플릿 리터럴을 잇기 위해 state를 받고 갱신해 돌려준다.
|
|
75
|
+
* @param {string} line
|
|
76
|
+
* @param {{ inBlock: boolean, inTemplate: boolean }} state - 제자리 갱신
|
|
77
|
+
* @returns {string} 코드 구간만 남은 문자열
|
|
78
|
+
*/
|
|
79
|
+
function stripNonCodeLine(line, state) {
|
|
80
|
+
let rest = line;
|
|
81
|
+
let code = '';
|
|
82
|
+
while (rest.length > 0) {
|
|
83
|
+
if (state.inBlock || state.inTemplate) {
|
|
84
|
+
const closer = state.inBlock ? '*/' : '`';
|
|
85
|
+
const end = findUnescaped(rest, closer);
|
|
86
|
+
if (end === -1) break;
|
|
87
|
+
if (state.inBlock) state.inBlock = false;
|
|
88
|
+
else state.inTemplate = false;
|
|
89
|
+
rest = rest.slice(end + closer.length);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const opener = rest.match(/\/\*|\/\/|`|'|"/);
|
|
93
|
+
if (!opener) return code + rest;
|
|
94
|
+
code += rest.slice(0, opener.index);
|
|
95
|
+
const token = opener[0];
|
|
96
|
+
rest = rest.slice(opener.index + token.length);
|
|
97
|
+
if (token === '//') break;
|
|
98
|
+
if (token === '/*') { state.inBlock = true; continue; }
|
|
99
|
+
if (token === '`') { state.inTemplate = true; continue; }
|
|
100
|
+
const close = findUnescaped(rest, token); // 홑/겹따옴표는 줄을 넘지 않는다
|
|
101
|
+
rest = close === -1 ? '' : rest.slice(close + 1);
|
|
102
|
+
}
|
|
103
|
+
return code;
|
|
104
|
+
}
|
|
105
|
+
|
|
102
106
|
/**
|
|
103
107
|
* P1: any 타입 탐지 — .ts/.tsx 전용, 단어 경계 기반.
|
|
108
|
+
*
|
|
109
|
+
* 주석·문자열·템플릿 리터럴은 제외한다: `any` 를 **금지하는** 문서 문장이
|
|
110
|
+
* 그 자체로 P1 이 되면, 고칠 수도 없는 경고가 그 파일을 편집할 때마다
|
|
111
|
+
* 주입돼 게이트 신뢰도가 떨어진다 (detectConsoleLogs의 확장자 게이트와 같은 이유).
|
|
112
|
+
*
|
|
104
113
|
* @param {string[]} lines
|
|
105
114
|
* @returns {Array<{ line: number, match: string, severity: 'P1' }>}
|
|
106
115
|
*/
|
|
107
116
|
function detectAnyType(lines) {
|
|
108
117
|
const findings = [];
|
|
118
|
+
const state = { inBlock: false, inTemplate: false };
|
|
109
119
|
lines.forEach((line, i) => {
|
|
120
|
+
const code = stripNonCodeLine(line, state);
|
|
110
121
|
for (const re of P1_DETECTORS) {
|
|
111
|
-
if (re.test(
|
|
122
|
+
if (re.test(code)) {
|
|
112
123
|
findings.push({
|
|
113
124
|
line: i + 1,
|
|
114
125
|
match: line.trim(),
|
|
@@ -125,21 +136,27 @@ function detectAnyType(lines) {
|
|
|
125
136
|
/**
|
|
126
137
|
* P1: console.log 탐지 — 허용 경로가 아닌 곳의 src/ 코드.
|
|
127
138
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
139
|
+
* 두 겹의 게이트를 쓴다:
|
|
140
|
+
* 1. 확장자 — 마크다운·JSON·텍스트에 인용된 `console.log(` 는 커밋되면 안 되는
|
|
141
|
+
* 디버그 코드가 아니라 문서상의 예시다. 이게 없으면 SKILL.md 안의 스니펫이
|
|
142
|
+
* 매 편집마다 P1 으로 주입된다.
|
|
143
|
+
* 2. 코드 구간 — 코드 파일 **안에서도** JSDoc 예시와 사용자에게 출력할 마크다운
|
|
144
|
+
* 템플릿 리터럴에 `console.log(` 가 등장한다. 원본 라인을 그대로 검사하면
|
|
145
|
+
* 그것들이 전부 P1 이 되어, 고칠 수 없는 경고가 반복되고 게이트 신뢰도가
|
|
146
|
+
* 떨어진다 — detectAnyType 이 stripNonCodeLine 을 쓰는 것과 같은 이유다.
|
|
147
|
+
* (2026-07-28 감사: 실측 9건 중 7건이 이 오탐이었다)
|
|
132
148
|
*
|
|
133
149
|
* @param {string[]} lines
|
|
134
150
|
* @param {string} filePath
|
|
135
151
|
* @returns {Array<{ line: number, match: string, severity: 'P1' }>}
|
|
136
152
|
*/
|
|
137
153
|
function detectConsoleLogs(lines, filePath) {
|
|
138
|
-
if (!
|
|
139
|
-
if (isConsoleAllowed(filePath)) return [];
|
|
154
|
+
if (!shouldCheckConsole(filePath)) return [];
|
|
140
155
|
const findings = [];
|
|
156
|
+
const state = { inBlock: false, inTemplate: false };
|
|
141
157
|
lines.forEach((line, i) => {
|
|
142
|
-
|
|
158
|
+
const code = stripNonCodeLine(line, state);
|
|
159
|
+
if (/console\.log\(/.test(code)) {
|
|
143
160
|
findings.push({
|
|
144
161
|
line: i + 1,
|
|
145
162
|
match: line.trim(),
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ANCHOR — 루프 회전 시작 시 디스크에서 상태를 재고정한다.
|
|
3
|
+
*
|
|
4
|
+
* loop-contract 는 ANCHOR 를 컨텍스트 오염 방어의 근거로 규정하는데, JUDGE·RECORD·stuck 과
|
|
5
|
+
* 달리 실행 수단이 없어 모델 재량으로 남아 있었다 (감사 2026-07-28 L3). 재고정 대상이
|
|
6
|
+
* 무엇인지 결정론적으로 답하는 것이 이 모듈의 역할이다 — 파일 내용을 해석하지는 않는다.
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { readLedger } from './run-ledger.js';
|
|
11
|
+
|
|
12
|
+
/** 우선순위대로 첫 번째로 존재하는 경로를 고른다 */
|
|
13
|
+
function firstExisting(projectDir, candidates) {
|
|
14
|
+
for (const rel of candidates) {
|
|
15
|
+
if (fs.existsSync(path.join(projectDir, rel))) return rel;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** `.vibe/.last-feature` 에 기록된 직전 feature 이름 */
|
|
21
|
+
function readLastFeature(projectDir) {
|
|
22
|
+
try {
|
|
23
|
+
const raw = fs.readFileSync(path.join(projectDir, '.vibe', '.last-feature'), 'utf-8').trim();
|
|
24
|
+
return raw.length > 0 ? raw : null;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** SPEC 경로 — 신규 레이아웃 우선, 레거시 폴백 */
|
|
31
|
+
function findSpec(projectDir, feature) {
|
|
32
|
+
if (!feature) return null;
|
|
33
|
+
return firstExisting(projectDir, [
|
|
34
|
+
path.join('.vibe', 'specs', `${feature}.md`),
|
|
35
|
+
path.join('.vibe', 'specs', feature, '_index.md'),
|
|
36
|
+
path.join('.claude', 'vibe', 'specs', `${feature}.md`),
|
|
37
|
+
path.join('.claude', 'specs', `${feature}.md`),
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 인박스에서 가장 최근 블록(다음 `## ` 직전까지) */
|
|
42
|
+
function readLatestInboxBlock(projectDir) {
|
|
43
|
+
try {
|
|
44
|
+
const raw = fs.readFileSync(path.join(projectDir, '.vibe', 'loops', 'inbox.md'), 'utf-8');
|
|
45
|
+
const start = raw.indexOf('## ');
|
|
46
|
+
if (start === -1) return null;
|
|
47
|
+
const next = raw.indexOf('\n## ', start + 3);
|
|
48
|
+
return (next === -1 ? raw.slice(start) : raw.slice(start, next)).trim() || null;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 재고정 번들 — loop-contract ANCHOR 절이 지정한 SPEC + run-ledger + scope.json + 직전 인박스.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} projectDir
|
|
58
|
+
* @param {string} [feature] - 생략 시 `.vibe/.last-feature`
|
|
59
|
+
* @returns {{ feature: string|null, spec: string|null, scope: string|null,
|
|
60
|
+
* ledger: object|null, latestInbox: string|null, missing: string[] }}
|
|
61
|
+
*/
|
|
62
|
+
export function buildAnchor(projectDir, feature) {
|
|
63
|
+
const resolved = feature || readLastFeature(projectDir);
|
|
64
|
+
const spec = findSpec(projectDir, resolved);
|
|
65
|
+
const scope = firstExisting(projectDir, [path.join('.vibe', 'scope.json')]);
|
|
66
|
+
const ledger = readLedger(projectDir);
|
|
67
|
+
|
|
68
|
+
const missing = [];
|
|
69
|
+
if (!resolved) missing.push('feature');
|
|
70
|
+
if (!spec) missing.push('spec');
|
|
71
|
+
if (!ledger) missing.push('run-ledger');
|
|
72
|
+
|
|
73
|
+
return { feature: resolved, spec, scope, ledger, latestInbox: readLatestInboxBlock(projectDir), missing };
|
|
74
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* console.log 탐지의 적용 범위 판단 — code-check.js·post-edit.js 공용 (중복 제거).
|
|
3
|
+
*
|
|
4
|
+
* 두 훅이 같은 검사를 각자 들고 있으면 한쪽의 허용 경로 설계가 다른 쪽 경고에
|
|
5
|
+
* 무력화된다. 범위 규칙은 여기 하나만 둔다.
|
|
6
|
+
*/
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { PROJECT_DIR, readProjectConfig } from '../utils.js';
|
|
9
|
+
import { globToRegExp } from './glob.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 코드 확장자 — 마크다운·JSON·텍스트에 인용된 `console.log(` 는 커밋되면 안 되는
|
|
13
|
+
* 디버그 코드가 아니라 문서상의 예시다.
|
|
14
|
+
*/
|
|
15
|
+
export const CODE_EXT_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
16
|
+
|
|
17
|
+
// console.log 기본 허용 경로 (glob 패턴 → 정규식으로 변환)
|
|
18
|
+
const DEFAULT_CONSOLE_ALLOW_GLOBS = [
|
|
19
|
+
'hooks/scripts/**',
|
|
20
|
+
'scripts/**',
|
|
21
|
+
'**/cli/**',
|
|
22
|
+
'**/*.test.*',
|
|
23
|
+
'**/*.spec.*',
|
|
24
|
+
'**/__tests__/**',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* .vibe/config.json의 qualityCheck.consoleAllow 글로브 목록 로드.
|
|
29
|
+
* 기본 글로브와 병합하여 반환.
|
|
30
|
+
* @returns {RegExp[]}
|
|
31
|
+
*/
|
|
32
|
+
function loadConsoleAllowPatterns() {
|
|
33
|
+
try {
|
|
34
|
+
const cfg = readProjectConfig();
|
|
35
|
+
const extra = cfg?.qualityCheck?.consoleAllow;
|
|
36
|
+
const globs = Array.isArray(extra)
|
|
37
|
+
? [...DEFAULT_CONSOLE_ALLOW_GLOBS, ...extra]
|
|
38
|
+
: DEFAULT_CONSOLE_ALLOW_GLOBS;
|
|
39
|
+
return globs.map(g => globToRegExp(g));
|
|
40
|
+
} catch {
|
|
41
|
+
return DEFAULT_CONSOLE_ALLOW_GLOBS.map(g => globToRegExp(g));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 파일 경로가 console.log 허용 경로인지 판단.
|
|
47
|
+
* @param {string} filePath - 절대 또는 프로젝트 상대 경로
|
|
48
|
+
* @returns {boolean}
|
|
49
|
+
*/
|
|
50
|
+
export function isConsoleAllowed(filePath) {
|
|
51
|
+
try {
|
|
52
|
+
const rel = path.relative(PROJECT_DIR, path.resolve(filePath)).replace(/\\/g, '/');
|
|
53
|
+
return loadConsoleAllowPatterns().some(re => re.test(rel));
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* console.log 검사 대상 파일인지 — 코드 확장자이면서 허용 경로가 아닌 경우.
|
|
61
|
+
* @param {string} filePath
|
|
62
|
+
* @returns {boolean}
|
|
63
|
+
*/
|
|
64
|
+
export function shouldCheckConsole(filePath) {
|
|
65
|
+
return CODE_EXT_RE.test(filePath) && !isConsoleAllowed(filePath);
|
|
66
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hook dispatcher library — 여러 hook script를 단일 이벤트에서
|
|
2
|
+
* Hook dispatcher library — 여러 hook script를 단일 이벤트에서 실행.
|
|
3
3
|
*
|
|
4
4
|
* 목적:
|
|
5
5
|
* - stdin을 한 번만 읽어 각 자식에 동일 버퍼로 pipe (중복 파싱/읽기 방지)
|
|
@@ -7,14 +7,23 @@
|
|
|
7
7
|
* - 한 스크립트 실패가 다른 스크립트를 막지 않도록 cascade 격리
|
|
8
8
|
* - PreToolUse 계열: 자식 중 하나라도 exit 2(deny)면 상위에 전파
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
10
|
+
* 실행 모델이 둘로 갈린다 — 스텝이 서로 독립인지가 기준:
|
|
11
|
+
*
|
|
12
|
+
* dispatch() = 순차 (spawn). 유일한 사용처인 Stop 은 스텝끼리
|
|
13
|
+
* 부작용을 공유한다(auto-commit 의 git 상태를
|
|
14
|
+
* devlog-gen 이 읽는다). 병렬화하면 auto-commit 의
|
|
15
|
+
* git cascade 와 겹쳐 프로세스가 폭주하고,
|
|
16
|
+
* devlog 가 커밋 이전 상태를 관측한다.
|
|
17
|
+
* 회귀 방지: __tests__/stop-dispatcher-sequential.test.js
|
|
18
|
+
*
|
|
19
|
+
* dispatchInProcess() = 병렬 (import). PreToolUse 가드는 모두 독립적
|
|
20
|
+
* 검증자라 순서가 의미 없고, 직렬 실행은 tool당
|
|
21
|
+
* 150~300ms 누적 오버헤드를 유발한다.
|
|
22
|
+
* 트레이드오프:
|
|
23
|
+
* - early-deny 낭비: sentinel-guard가 block이어도
|
|
24
|
+
* pre-tool/scope-guard가 이미 실행됨. 실측 μs 수준.
|
|
25
|
+
* - stderr 인터리빙: 가드 2개가 동시 block 시 경고가
|
|
26
|
+
* 섞일 수 있음. 각 메시지가 완결된 라인이라 무해.
|
|
18
27
|
*/
|
|
19
28
|
import { spawn } from 'child_process';
|
|
20
29
|
import path from 'path';
|
|
@@ -47,6 +56,44 @@ function isEnabled(hookConfig, name) {
|
|
|
47
56
|
return true;
|
|
48
57
|
}
|
|
49
58
|
|
|
59
|
+
/**
|
|
60
|
+
* guard 크래시를 deny 로 승격할지 — `VIBE_HOOK_FAILCLOSED=0` 이면 끈다.
|
|
61
|
+
*
|
|
62
|
+
* fail-closed 는 "검증하지 못했으면 통과시키지 않는다" 는 안전한 기본값이지만,
|
|
63
|
+
* guard 에 버그가 생기면 Edit·Bash 가 전면 차단되어 guard 소스를 고칠 수단까지
|
|
64
|
+
* 막힌다. 그래서 탈출구는 선택이 아니라 **복구 유일 수단**이고, 차단 메시지가
|
|
65
|
+
* 스스로 그 방법을 알려줘야 한다 (교착에 빠진 사용자는 문서를 찾아볼 수 없다).
|
|
66
|
+
*/
|
|
67
|
+
function isFailClosed() {
|
|
68
|
+
return process.env.VIBE_HOOK_FAILCLOSED !== '0';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* step 크래시를 stderr 에 드러낸다. 침묵하면 게이트가 죽은 줄도 모른다.
|
|
73
|
+
* @param {string} name
|
|
74
|
+
* @param {unknown} err
|
|
75
|
+
*/
|
|
76
|
+
function reportCrash(name, err) {
|
|
77
|
+
const message = (err && err.message) || String(err);
|
|
78
|
+
process.stderr.write(`[vibe] hook step "${name}" crashed: ${message}\n`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 크래시한 step 의 종료 코드를 정한다.
|
|
83
|
+
* deny 권한(denyOnExit2)이 있는 guard 만 2(deny)로 승격한다 — 로깅용 step 하나가
|
|
84
|
+
* 죽었다고 도구 호출을 막을 이유는 없다.
|
|
85
|
+
* @param {{ name: string, denyOnExit2?: boolean }} step
|
|
86
|
+
* @returns {number}
|
|
87
|
+
*/
|
|
88
|
+
function crashExitCode(step) {
|
|
89
|
+
if (!step.denyOnExit2 || !isFailClosed()) return 1;
|
|
90
|
+
process.stderr.write(
|
|
91
|
+
`[vibe] blocked: guard "${step.name}" could not complete, so the operation was not verified.\n` +
|
|
92
|
+
` To bypass temporarily, re-run with VIBE_HOOK_FAILCLOSED=0\n`
|
|
93
|
+
);
|
|
94
|
+
return 2;
|
|
95
|
+
}
|
|
96
|
+
|
|
50
97
|
async function readStdin() {
|
|
51
98
|
if (process.stdin.isTTY) return '';
|
|
52
99
|
let data = '';
|
|
@@ -93,7 +140,11 @@ function buildChildEnv(stdinData) {
|
|
|
93
140
|
}
|
|
94
141
|
|
|
95
142
|
/**
|
|
96
|
-
* 디스패처 실행 — 활성화된 스텝을
|
|
143
|
+
* 디스패처 실행 — 활성화된 스텝을 선언 순서대로 **순차** spawn.
|
|
144
|
+
*
|
|
145
|
+
* 순차인 이유는 파일 상단 주석 참고 — 스텝이 git 상태 같은 부작용을 공유한다.
|
|
146
|
+
* 앞 스텝이 실패해도 다음 스텝은 계속 실행한다(cascade 격리 유지).
|
|
147
|
+
*
|
|
97
148
|
* @param {Array<{name: string, script: string, args?: string[], denyOnExit2?: boolean, timeoutMs?: number}>} steps
|
|
98
149
|
*/
|
|
99
150
|
export async function dispatch(steps) {
|
|
@@ -101,12 +152,11 @@ export async function dispatch(steps) {
|
|
|
101
152
|
const hookConfig = loadHookConfig();
|
|
102
153
|
|
|
103
154
|
const enabledSteps = steps.filter(s => isEnabled(hookConfig, s.name));
|
|
104
|
-
const results =
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
);
|
|
155
|
+
const results = [];
|
|
156
|
+
for (const step of enabledSteps) {
|
|
157
|
+
const code = await runScript(step.script, step.args || [], stdinData, step.timeoutMs || 30000);
|
|
158
|
+
results.push({ step, code });
|
|
159
|
+
}
|
|
110
160
|
|
|
111
161
|
// 하나라도 deny(exit 2) 반환 → 상위에 전파
|
|
112
162
|
if (results.some(({ step, code }) => step.denyOnExit2 && code === 2)) {
|
|
@@ -138,9 +188,11 @@ export async function dispatchInProcess(steps, { argvToolName = '' } = {}) {
|
|
|
138
188
|
enabledSteps.map(async (step) => {
|
|
139
189
|
try {
|
|
140
190
|
return { step, code: await step.run(ctx) };
|
|
141
|
-
} catch {
|
|
142
|
-
// 크래시
|
|
143
|
-
|
|
191
|
+
} catch (err) {
|
|
192
|
+
// 크래시 격리는 유지하되 침묵하지 않는다. deny 권한이 있는 guard 는
|
|
193
|
+
// 검증에 실패한 것이므로 통과시키지 않는다 (fail-closed).
|
|
194
|
+
reportCrash(step.name, err);
|
|
195
|
+
return { step, code: crashExitCode(step) };
|
|
144
196
|
}
|
|
145
197
|
})
|
|
146
198
|
);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 루프 인박스 — 사람 리뷰 큐(`.vibe/loops/inbox.md`) 기록.
|
|
3
|
+
*
|
|
4
|
+
* loop-history.jsonl 은 결정론적으로 기록되는데 인박스만 모델이 마크다운을 직접
|
|
5
|
+
* prepend 하고 있었다 (감사 2026-07-28 L5). 블록 형식과 최신순 정렬을 코드가 보장한다.
|
|
6
|
+
*
|
|
7
|
+
* fail-open — 기록 실패가 루프를 멈추지 않는다.
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
const HEADER = '# Loop Inbox\n\n> 루프가 남긴 사람 리뷰 큐. 최신 항목이 위에 온다.\n';
|
|
13
|
+
|
|
14
|
+
function inboxPath(projectDir) {
|
|
15
|
+
return path.join(projectDir, '.vibe', 'loops', 'inbox.md');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 인박스 블록을 최상단에 prepend 한다.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} projectDir
|
|
22
|
+
* @param {{ loop: string, result: 'ok'|'fail'|'stuck', at: string, lines?: string[] }} entry
|
|
23
|
+
* at 은 호출자가 넘긴다 — 이 모듈은 시각을 직접 읽지 않는다 (테스트 결정성)
|
|
24
|
+
* @returns {boolean} 성공 여부
|
|
25
|
+
*/
|
|
26
|
+
export function prependInboxBlock(projectDir, entry) {
|
|
27
|
+
try {
|
|
28
|
+
if (!entry?.loop || !entry?.result || !entry?.at) return false;
|
|
29
|
+
|
|
30
|
+
const body = (entry.lines ?? []).map(l => `- ${l}`).join('\n');
|
|
31
|
+
const block = `## ${entry.loop} — ${entry.at} — ${entry.result}\n${body}\n`;
|
|
32
|
+
|
|
33
|
+
const target = inboxPath(projectDir);
|
|
34
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
35
|
+
|
|
36
|
+
const existing = fs.existsSync(target) ? fs.readFileSync(target, 'utf-8') : '';
|
|
37
|
+
const blocksStart = existing.indexOf('## ');
|
|
38
|
+
const head = blocksStart === -1 ? HEADER : existing.slice(0, blocksStart);
|
|
39
|
+
const rest = blocksStart === -1 ? '' : existing.slice(blocksStart);
|
|
40
|
+
|
|
41
|
+
fs.writeFileSync(target, `${head}\n${block}\n${rest}`.replace(/\n{3,}/g, '\n\n'), 'utf-8');
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 아직 처리되지 않은 블록 수 — `## ` 로 시작하는 줄의 개수.
|
|
50
|
+
* @param {string} projectDir
|
|
51
|
+
* @returns {number}
|
|
52
|
+
*/
|
|
53
|
+
export function countInboxBlocks(projectDir) {
|
|
54
|
+
try {
|
|
55
|
+
const raw = fs.readFileSync(inboxPath(projectDir), 'utf-8');
|
|
56
|
+
return raw.split('\n').filter(l => l.startsWith('## ')).length;
|
|
57
|
+
} catch {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -6,12 +6,17 @@
|
|
|
6
6
|
* node hooks/scripts/loop-ledger.js start <name>
|
|
7
7
|
* node hooks/scripts/loop-ledger.js end <name> <ok|fail|stuck> [summary]
|
|
8
8
|
* node hooks/scripts/loop-ledger.js check-stuck <name> <discoverHash>
|
|
9
|
+
* node hooks/scripts/loop-ledger.js anchor [feature]
|
|
10
|
+
* node hooks/scripts/loop-ledger.js inbox <name> <ok|fail|stuck> [line...]
|
|
9
11
|
*
|
|
10
12
|
* check-stuck: 'stuck' 또는 'ok'를 stdout에 출력하고 항상 exit 0.
|
|
13
|
+
* anchor: 재고정 번들 JSON을 stdout에 출력한다 (loop-contract ANCHOR 절).
|
|
11
14
|
* 항상 exit 0 (fail-open).
|
|
12
15
|
*/
|
|
13
16
|
|
|
14
17
|
import { appendLoopEvent, isStuck } from './lib/loop-ledger.js';
|
|
18
|
+
import { buildAnchor } from './lib/anchor.js';
|
|
19
|
+
import { prependInboxBlock } from './lib/inbox.js';
|
|
15
20
|
|
|
16
21
|
const [, , subcommand, ...args] = process.argv;
|
|
17
22
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
@@ -47,9 +52,27 @@ if (subcommand === 'start') {
|
|
|
47
52
|
appendLoopEvent(projectDir, { loop, event: 'discover', discoverHash });
|
|
48
53
|
process.stdout.write(stuck ? 'stuck\n' : 'ok\n');
|
|
49
54
|
|
|
55
|
+
} else if (subcommand === 'anchor') {
|
|
56
|
+
// 회전 시작 시 디스크 재고정 — 모델이 무엇을 다시 읽어야 하는지 결정론적으로 답한다
|
|
57
|
+
process.stdout.write(JSON.stringify(buildAnchor(projectDir, args[0]), null, 2) + '\n');
|
|
58
|
+
|
|
59
|
+
} else if (subcommand === 'inbox') {
|
|
60
|
+
const [loop, result, ...lines] = args;
|
|
61
|
+
if (!loop || !result) {
|
|
62
|
+
process.stdout.write('[loop-ledger] error: inbox 에 루프 이름과 결과(ok|fail|stuck)가 필요합니다\n');
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
const at = new Date().toISOString();
|
|
66
|
+
const ok = prependInboxBlock(projectDir, { loop, result, at, lines });
|
|
67
|
+
process.stdout.write(
|
|
68
|
+
ok ? `[loop-ledger] inbox recorded: loop=${loop} result=${result}\n`
|
|
69
|
+
: '[loop-ledger] WARNING: inbox write failed\n'
|
|
70
|
+
);
|
|
71
|
+
|
|
50
72
|
} else {
|
|
51
73
|
process.stdout.write(
|
|
52
|
-
'[loop-ledger] 사용법: start <name> | end <name> <ok|fail|stuck> [summary] |
|
|
74
|
+
'[loop-ledger] 사용법: start <name> | end <name> <ok|fail|stuck> [summary] | '
|
|
75
|
+
+ 'check-stuck <name> <hash> | anchor [feature] | inbox <name> <ok|fail|stuck> [line...]\n'
|
|
53
76
|
);
|
|
54
77
|
}
|
|
55
78
|
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
* auto-format — 코드 스타일 정규화 (변경 시 finding 반환)
|
|
11
11
|
* code-check — 하드룰(any/console.log) 탐지 (additionalContext 주입만, 커밋 게이트 미연동)
|
|
12
12
|
* auto-test — 관련 테스트 실행 (debounce 지원)
|
|
13
|
-
*
|
|
13
|
+
*
|
|
14
|
+
* post-edit.js 는 여기서 돌리지 않는다 — console.log 감지는 code-check 가 같은
|
|
15
|
+
* 허용 경로 규칙(lib/console-allow.js)으로 이미 수행한다. 둘 다 돌리면 허용
|
|
16
|
+
* 경로에서도 경고가 남는다. post-edit.js 는 antigravity-hooks.json 단독 등록용.
|
|
14
17
|
*
|
|
15
18
|
* 출력 계약 (Claude Code PostToolUse):
|
|
16
19
|
* findings 있음 → stdout에 JSON hookSpecificOutput 1개 출력, exit 0
|
|
@@ -33,7 +36,6 @@ import path from 'path';
|
|
|
33
36
|
import { run as autoFormat } from './auto-format.js';
|
|
34
37
|
import { run as codeCheck } from './code-check.js';
|
|
35
38
|
import { run as autoTest } from './auto-test.js';
|
|
36
|
-
import { run as postEdit } from './post-edit.js';
|
|
37
39
|
|
|
38
40
|
// ─── 설정 로딩 ────────────────────────────────────────────────────────
|
|
39
41
|
function loadHookConfig() {
|
|
@@ -59,7 +61,6 @@ const steps = [
|
|
|
59
61
|
{ name: 'auto-format', run: autoFormat },
|
|
60
62
|
{ name: 'code-check', run: codeCheck },
|
|
61
63
|
{ name: 'auto-test', run: autoTest },
|
|
62
|
-
{ name: 'post-edit', run: postEdit },
|
|
63
64
|
];
|
|
64
65
|
|
|
65
66
|
const enabledSteps = steps.filter(s => isEnabled(hookConfig, s.name));
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
import { existsSync, readFileSync } from 'fs';
|
|
11
11
|
import path from 'path';
|
|
12
12
|
import { buildCliCtx, isDirectRun } from './lib/hook-context.js';
|
|
13
|
+
import { shouldCheckConsole } from './lib/console-allow.js';
|
|
13
14
|
|
|
14
15
|
const CONSOLE_LOG_RE = /console\.log\(/;
|
|
15
|
-
const CODE_EXT_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* in-process 진입점 — console.log 감지만 수행.
|
|
@@ -25,7 +25,7 @@ export async function run(ctx) {
|
|
|
25
25
|
try {
|
|
26
26
|
const filePath = ctx.filePath;
|
|
27
27
|
|
|
28
|
-
if (filePath &&
|
|
28
|
+
if (filePath && shouldCheckConsole(filePath)) {
|
|
29
29
|
const resolved = path.resolve(filePath);
|
|
30
30
|
if (existsSync(resolved)) {
|
|
31
31
|
const lines = readFileSync(resolved, 'utf-8').split('\n');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@su-record/vibe",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.15",
|
|
4
4
|
"description": "AI Coding Framework for Claude Code — 7+ agents, 52 skills, multi-LLM orchestration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli/index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"vibe": "dist/cli/index.js"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
|
-
"build": "tsc && chmod +x dist/cli/index.js",
|
|
24
|
+
"build": "tsc -p tsconfig.build.json && chmod +x dist/cli/index.js",
|
|
25
25
|
"dev": "tsc --watch",
|
|
26
26
|
"gen:skill-docs": "npx tsx scripts/gen-skill-docs.ts",
|
|
27
27
|
"gen:skill-docs:check": "npx tsx scripts/gen-skill-docs.ts --check",
|
|
@@ -31,8 +31,9 @@
|
|
|
31
31
|
"validate:counts": "npx tsx scripts/validate-counts.ts",
|
|
32
32
|
"test": "vitest run",
|
|
33
33
|
"test:watch": "vitest",
|
|
34
|
+
"test:coverage": "vitest run --coverage",
|
|
34
35
|
"prepublishOnly": "pnpm build",
|
|
35
|
-
"postinstall": "node -e \"import('./dist/cli/postinstall/main.js').then(m=>m.main()).catch(()
|
|
36
|
+
"postinstall": "node -e \"import('./dist/cli/postinstall/main.js').then(m=>m.main()).catch(e=>process.stderr.write('\\u26a0\\ufe0f vibe postinstall skipped: '+((e&&e.message)||e)+'\\n Assets and hooks were not installed. Run: npx vibe upgrade\\n'))\"",
|
|
36
37
|
"release": "pnpm version patch && git push origin main --follow-tags"
|
|
37
38
|
},
|
|
38
39
|
"keywords": [
|
|
@@ -66,16 +67,17 @@
|
|
|
66
67
|
"access": "public"
|
|
67
68
|
},
|
|
68
69
|
"engines": {
|
|
69
|
-
"node": ">=
|
|
70
|
+
"node": ">=20.12.0"
|
|
70
71
|
},
|
|
72
|
+
"packageManager": "pnpm@9.15.9",
|
|
71
73
|
"optionalDependencies": {
|
|
72
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.
|
|
74
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.38",
|
|
73
75
|
"@ast-grep/napi": "^0.40.5"
|
|
74
76
|
},
|
|
75
77
|
"dependencies": {
|
|
76
78
|
"@clack/prompts": "^1.0.0",
|
|
77
79
|
"better-sqlite3": "^12.9.0",
|
|
78
|
-
"chalk": "^5.
|
|
80
|
+
"chalk": "^5.6.2",
|
|
79
81
|
"glob": "^13.0.1",
|
|
80
82
|
"papaparse": "^5.5.3",
|
|
81
83
|
"ts-morph": "^26.0.0",
|
|
@@ -84,11 +86,12 @@
|
|
|
84
86
|
},
|
|
85
87
|
"devDependencies": {
|
|
86
88
|
"@types/better-sqlite3": "^7.6.13",
|
|
87
|
-
"@types/node": "^22.
|
|
89
|
+
"@types/node": "^22.19.10",
|
|
88
90
|
"@types/papaparse": "^5.5.2",
|
|
91
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
89
92
|
"ajv": "^8.17.1",
|
|
90
|
-
"typescript": "^5.
|
|
91
|
-
"vitest": "^4.
|
|
93
|
+
"typescript": "^5.9.3",
|
|
94
|
+
"vitest": "^4.1.10"
|
|
92
95
|
},
|
|
93
96
|
"files": [
|
|
94
97
|
"dist/",
|
|
@@ -97,6 +100,7 @@
|
|
|
97
100
|
"agents/",
|
|
98
101
|
"skills/",
|
|
99
102
|
"hooks/",
|
|
103
|
+
"!hooks/scripts/__tests__",
|
|
100
104
|
".env.example",
|
|
101
105
|
"CLAUDE.md",
|
|
102
106
|
"README.md",
|