phasegate 0.340.0 → 0.341.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/docs/folder_management_rules.md +10 -1
  3. package/docs/guide/cli-reference.md +23 -3
  4. package/docs/guide/configuration.md +28 -2
  5. package/docs/guide/hooks-integration.md +7 -1
  6. package/docs/guide/skills-overview.md +3 -0
  7. package/package.json +2 -1
  8. package/scripts/harness/agent-integration/application/usecases/handle-post-tool-use-usecase.ts +4 -0
  9. package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +36 -1
  10. package/scripts/harness/agent-integration/application/usecases/handle-stop-usecase.ts +3 -9
  11. package/scripts/harness/agent-integration/domain/ports/story-reflection-query-port.ts +2 -0
  12. package/scripts/harness/agent-integration/domain/services/hook-to-cli-translator.ts +8 -5
  13. package/scripts/harness/agent-integration/domain/value-objects/hook-translation-result.ts +1 -1
  14. package/scripts/harness/agent-integration/domain/value-objects/story-reflection-query-result.ts +10 -2
  15. package/scripts/harness/agent-integration/domain/value-objects/write-target-scope.ts +12 -0
  16. package/scripts/harness/agent-integration/infrastructure/adapters/child-process-cli-executor-adapter.ts +64 -13
  17. package/scripts/harness/agent-integration/infrastructure/adapters/file-system-story-reflection-query-adapter.ts +108 -2
  18. package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +8 -6
  19. package/scripts/harness/agent-integration/presentation/hook-skip-event-recorder.ts +3 -0
  20. package/scripts/harness/agent-integration/presentation/post-tool-use-feedback.ts +39 -0
  21. package/scripts/harness/agent-integration/presentation/post-tool-use-hook.ts +20 -27
  22. package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +3 -0
  23. package/scripts/harness/agent-integration/presentation/stop-hook.ts +1 -3
  24. package/scripts/harness/biome-ast-engine/infrastructure/adapters/biome-cli-executor-adapter.ts +21 -1
  25. package/scripts/harness/biome-ast-engine/infrastructure/adapters/typescript-source-module-analyzer-adapter.ts +5 -0
  26. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +15 -0
  27. package/scripts/harness/config-foundation/infrastructure/validators/ajv-config-schema-validator.ts +6 -3
  28. package/scripts/harness/harness-api/domain/ports/biome-lint-port.ts +2 -1
  29. package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +12 -1
  30. package/scripts/harness/harness-api/infrastructure/adapters/biome-ast-engine-lint-adapter.ts +10 -2
  31. package/scripts/harness/installation/application/bundled-skill-selection.ts +28 -2
  32. package/scripts/harness/installation/application/checks/check-utils.ts +2 -2
  33. package/scripts/harness/installation/application/usecases/run-install.ts +5 -5
  34. package/scripts/harness/installation/application/usecases/run-reconcile.ts +62 -21
  35. package/scripts/harness/installation/presentation/cli/install-handler.ts +2 -1
  36. package/scripts/harness/main.ts +128 -90
  37. package/scripts/harness/phase-dependency-model/domain/services/story-reflection-checker.ts +48 -5
  38. package/scripts/harness/phase-dependency-model/domain/services/work-item-reflection-scope-resolver.ts +64 -0
  39. package/scripts/harness/phase-dependency-model/domain/values/story-reflection-mapping.ts +8 -2
  40. package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +6 -1
  41. package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-work-item-dependency-catalog.ts +73 -0
  42. package/scripts/harness/quick-mode/application/dto/change-category-classification-contract.ts +4 -0
  43. package/scripts/harness/quick-mode/application/ports/change-risk-advisory-port.ts +16 -0
  44. package/scripts/harness/quick-mode/composition-root.ts +5 -1
  45. package/scripts/harness/quick-mode/infrastructure/adapters/snapshot-risk-advisory-adapter.ts +118 -0
  46. package/scripts/harness/quick-mode/presentation/formatters/change-category-formatter.ts +7 -0
  47. package/scripts/harness/quick-mode/presentation/handlers/check-change-category-handler.ts +12 -1
  48. package/scripts/harness/setup/skill-deployer.ts +3 -45
  49. package/scripts/harness/skill-quality/application/dto/run-plan-checker-loop-output.ts +1 -0
  50. package/scripts/harness/skill-quality/application/usecases/apply-cascade-update-usecase.ts +9 -4
  51. package/scripts/harness/skill-quality/application/usecases/execute-tdd-cycle-usecase.ts +2 -1
  52. package/scripts/harness/skill-quality/application/usecases/run-plan-checker-loop-usecase.ts +8 -0
  53. package/scripts/harness/skill-quality/composition-root.ts +14 -7
  54. package/scripts/harness/skill-quality/domain/ports/plan-check-executor-port.ts +2 -0
  55. package/scripts/harness/skill-quality/domain/value-objects/cascade-update-target.ts +15 -1
  56. package/scripts/harness/skill-quality/infrastructure/adapters/l1-biome-validator-adapter.ts +9 -1
  57. package/scripts/harness/skill-quality/infrastructure/adapters/l2-validator-system-adapter.ts +18 -2
  58. package/scripts/harness/skill-quality/presentation/handlers/apply-cascade-update-handler.ts +8 -2
  59. package/scripts/harness/skill-quality/presentation/handlers/execute-tdd-cycle-handler.ts +4 -3
  60. package/scripts/harness/skill-quality/presentation/handlers/run-plan-checker-loop-handler.ts +11 -4
  61. package/scripts/harness/traceability-model/infrastructure/parsers/work-item-frontmatter-parser.ts +41 -0
  62. package/skills/README.md +7 -0
  63. package/skills/cascade-updater/SKILL.md +37 -109
  64. package/skills/cascade-updater/references//345/261/244/345/210/245/345/210/244/345/256/232/343/202/254/343/202/244/343/203/211.md +5 -3
  65. package/skills/release-publisher/SKILL.md +3 -1
  66. package/skills/skill-creator/SKILL.md +8 -9
package/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.341.0] - 2026-09-20
11
+
12
+ ### Changed
13
+
14
+ - **WI-220 — Hook and CLI runtime improvements** — Avoid unnecessary read-only/disabled hook work, preserve legacy hook settings, load CLI modules and schemas lazily, and distribute compiled runtime JavaScript through the tested runtime tarball. Improve child-process cleanup, interrupted configuration/reconcile recovery, dependency-scoped reflection diagnostics, and cascade/TDD traceability. <!-- @work-item-id WI-220 -->
15
+
16
+ ### Known limitations
17
+
18
+ - WI-220 remains incomplete: the absolute 500ms/format-only fast-path target, full semantic impact classification, and the agent reading comparison acceptance criterion remain unmet. This release does not mark those requirements as satisfied. <!-- @work-item-id WI-220 -->
19
+
20
+ ## Previous unreleased notes
21
+
10
22
  ### Added
11
23
 
12
24
  - **WI-385 — Grok Build / Antigravity pre-edit integration** — PreToolUse now detects flat snake_case, flat camelCase, and nested `toolCall` payloads structurally, maps Grok and Antigravity write vocabularies into the existing gate, and renders runtime-specific deny JSON without changing Claude/Codex stdout contracts. Install/init/setup/doctor accept `grok`, `antigravity`, and `all` while preserving `both=claude+codex`; lifecycle management adds the Antigravity named hook map, Grok/Antigravity structural doctor checks, trust/CLI-only notices, and explicit L2 backstop guidance. <!-- @work-item-id WI-385 -->
@@ -101,7 +101,16 @@ legacy_id: ISSUE-XXX | US-XXX | H{NN}-{NN} # 任意: 移行用エイ
101
101
  source: github#123 | slack | internal # 任意: 外部報告源
102
102
  ---
103
103
  ```
104
-
104
+ <!-- @work-item-id WI-220 -->
105
+
106
+ `depends_on` はWI間の直接依存宣言です。フェーズ設定の
107
+ `dependsOn` とは別の項目です。
108
+ - 明示的な `[]` は「依存なし」、未記載は「不明」とします。
109
+ - 既存WIへの一括追記は行いません。
110
+ - 依存情報が不正・不足している場合、部分的な依存集合だけで必要な反映検査を省きません。
111
+ - 依存宣言自体は、設計内容の反映や承認の証明ではありません。
112
+ - 既存の通常metadata検査は変更せず、依存チェックの警告/明示選択した強制化経路で追加診断します。
113
+
105
114
  ### 3.3 type による要求成果物の段階化
106
115
 
107
116
  WI の重さに応じて、生成必須の成果物が変わります。
@@ -39,6 +39,8 @@ Command names in this document are split into three surfaces:
39
39
 
40
40
  ### Setup JSON and report outputs
41
41
 
42
+ `config:plan` does not initialize or reconstruct a missing/unreadable/malformed configuration. For unreadable or malformed input, config-changing previews report `configPatch.applicability: "blocked"`. Missing input retains the legacy preview (`before: null`, `applicability: "applicable"`, partial patch), with `phasegate install --dry-run` listed first; this preview is not permission to create a partial configuration. In all three cases, `--apply` refuses with exit 1 without changing files. For damaged JSON, an authorized operator should restore a known-good copy or repair the JSON before retrying; do not repeatedly attempt protected direct writes. Valid config updates retain a byte-preserving backup. Existing schema-validation failures still exit 2. <!-- @work-item-id WI-220 -->
43
+
42
44
  <!-- @work-item-id WI-158 -->
43
45
 
44
46
  Setup lifecycle commands support JSON for automation where shown by help: `install --json`, `reconcile --json`, `uninstall --json`, and `doctor --json`. `doctor --agent claude --json` and `doctor --agent codex --json` include `scope` and `scopedOutFindings` so agents can distinguish selected-agent readiness from full-install diagnostics. Scoped-out findings suppress immediate repair guidance with `repairHint: null`, `suggestedSkill: null`, `currentScopeRepairTarget: false`, `repairHintApplicability: "only-if-agent-selected"`, and `repairModeApplicability: "only-if-agent-selected"`; applicable `findings[]` use `currentScopeRepairTarget: true` with applicable repair fields. `doctor --report-out <path>` persists the doctor JSON payload to that exact path. Relative paths are resolved from the project root; absolute paths are used as-is. <!-- @work-item-id WI-178, WI-179, WI-180 -->
@@ -67,6 +69,14 @@ This is separate from `reporting.outputDir`. The configured report directory is
67
69
 
68
70
  ### `check-change-category` の使い方
69
71
 
72
+ <!-- @work-item-id WI-220 -->
73
+
74
+ 任意の `--risk-snapshots snapshots.json` は既存分類とは別に `riskAdvice` を追加する。入力は `[{"filePath":"src/foo.ts","beforeContent":"...","afterContent":"..."}]`(内容はstringまたはnull)。filePathは `--paths` と完全一致させる。未指定時の出力・処理は従来どおり。指定しても `fullModeRequired` と `--fail-on-full-required` の終了コードは変えない。
75
+
76
+ 助言は `module-surface-change`(明示されたmodule宣言の差分)、`behavior-review`(宣言不変だが本文の意味は要確認)、`unknown`(情報不足・未対応)、`no-content-change`(提供された同文snapshotのみ)のいずれか。構文比較対象は明示型付きexport関数・interface・typeと、型が明示されたclass method/property/constructor。classの本文・property初期値は宣言比較から分離する。decorator、accessor、型推論、default引数、再export、宣言ファイル、create/delete等の未対応構文は不明になる。内部adapterであることや業務上の同値性をpathだけから認定しない。
77
+
78
+ snapshotは呼出元の比較資料であり、現在のファイル・上位承認との一致を検証した証拠ではない。出力のbeforeHash/afterHashで比較内容を特定し、実revisionと照合する。契約差分候補は上位契約と利用者への影響、本文変更は認可・不変条件・保存形式を確認する。不明は自動許可・追加の強制拒否のどちらにも変換しない。助言機能だけで意味リスク判定が完成したとは扱わない。
79
+
70
80
  ISSUE-006 Story A で導入。Quick Mode で取り扱おうとしている変更が
71
81
  `quickMode.fullModeRequiredWhen` のいずれかをトリガーするか事前に確認したいときに使う。
72
82
 
@@ -374,7 +384,7 @@ The following are binary subcommands (`npx phasegate <command>`). Do not assume
374
384
  | `phasegate:check-phase` | `--unit <unitId>` `--json` | Current phase for a unit |
375
385
  | `phasegate:ci-check` | `--json` | Full CI check (L2-L4; disabled L4 is reported as skipped) |
376
386
  | `phasegate:detect-drift` | `--json` | Design-code drift report |
377
- | `phasegate:lint` | `--target <path>` `--json` | Lint via harness-api |
387
+ | `phasegate:lint` | `--target <path>` (repeatable) `--json` | Analyze the full graph; restrict reported diagnostics to targets |
378
388
  | `phasegate:complete-check` | `--json` | L2-L4 full check |
379
389
  | `phasegate:impact-analysis` | `<storyId>` `--json` | Story impact analysis |
380
390
  | `phasegate:generate-matrix` | `--requirements <path>` `--tests <path>` `--out <path>` `--json` | Generate the requirement-test matrix |
@@ -490,12 +500,18 @@ ISSUE-005 P3-10 で明確化された境界:
490
500
 
491
501
  | Command | Options | Description |
492
502
  |---|---|---|
493
- | `skill:execute-tdd-cycle` | `--unit` `--story` `--desc` `--phase RED\|GREEN\|REFACTOR` `--passed` | Run TDD cycle |
503
+ | `skill:execute-tdd-cycle` | `--unit` `--story` `--desc` `--phase RED\|GREEN\|REFACTOR` `--passed` `--configured-validation` (optional) | Validate commit readiness and commit staged changes |
494
504
  | `skill:check-coverage` | `--story <storyId>` `--json` | Coverage check |
495
505
  | `skill:collect-lessons` | `--story <storyId>` `--sources <paths>` `--write-artifact` | Collect agent lessons |
496
- | `skill:apply-cascade-update` | `--story <storyId>` `--dry-run` | Cascade update to upstream docs |
506
+ | `skill:apply-cascade-update` | `--story <storyId>` `--dry-run` | Append traceability tags only; does not perform semantic design review. Counts changed files (planned changes in dry-run), not already-tagged files. |
497
507
  | `skill:validate-structure` | `--file <path>` `--json` | Validate skill structure |
498
508
 
509
+ `skill:apply-cascade-update --story WI-220` writes `@work-item-id WI-220`; legacy story IDs keep `@story-id`. Existing legacy annotations remain readable and unchanged. IDs are matched exactly, including comma/space-separated lists. Repeated target paths are processed once per invocation, including failed reads; fix the reported cause before explicitly retrying. JS/TS source annotations are comments so tagging does not invalidate source syntax. This command still performs tag updates only, not semantic design review. <!-- @work-item-id WI-220 -->
510
+
511
+ `skill:execute-tdd-cycle` does not run tests: `--passed` is the caller's assertion that tests passed. Run the relevant tests first. The command requires `REFACTOR` and `--passed`, runs its validation gates, and invokes a normal Git commit without bypassing hooks. With `--story WI-220` (or another `WI-<number>`), the commit includes a `Work-Item` trailer; legacy story IDs retain their existing subject without an inferred WI mapping.
512
+
513
+ By default, the TDD command preserves its legacy L1/L2/L3 validation profile, including warning-as-blocking behavior. Existing settings previously ignored by this command do not silently introduce new blockers after an upgrade. To explicitly adopt the resolved L1/architecture and L2/L3 settings, use `--configured-validation`. In that profile, coverage thresholds and enabled World checks can add required findings; L2/L3 warnings follow `validate.failOnWarning`, while L1 warnings still block. Neither profile adds L4, runs tests on behalf of `--passed`, or bypasses Git hooks. The selected profile is printed. Review configuration and run the required checks before selecting the configured profile. <!-- @work-item-id WI-220 -->
514
+
499
515
  ---
500
516
 
501
517
  ## CI/CD
@@ -557,3 +573,7 @@ phasegate validate --layer L3
557
573
  ```
558
574
 
559
575
  Use `--json` to inspect `missingTests`, `orphanTests`, preserved references, and intent coverage.
576
+ ## Consumer skill selection
577
+
578
+ <!-- @work-item-id WI-223 -->
579
+ `init --skills consumer` and `install --skills consumer --dry-run` select 27 bundled skills, excluding Phasegate's own release-publisher and skill-creator. Apply install explicitly with `--apply`. The existing core/all sets and default all remain supported. Reconcile preserves the recorded set independently for shared/personal skill roots. No automatic deletion or migration of existing skills occurs when selecting a smaller set.
@@ -1,5 +1,29 @@
1
1
  # Configuration
2
2
 
3
+ ## Work-item dependency declarations
4
+
5
+ <!-- @work-item-id WI-220 -->
6
+
7
+ A WI description may declare direct dependencies in its frontmatter:
8
+
9
+ ```yaml
10
+ depends_on: [WI-123, WI-124]
11
+ ```
12
+
13
+ A block list of WI IDs is also supported. An explicit `depends_on: []` means no declared dependencies; an omitted field means unknown, not an empty dependency set. Do not add empty declarations to old WIs without reviewing their dependencies. This is separate from phase configuration's `dependsOn` and is not evidence of semantic design approval.
14
+
15
+ The dependency-specific reader diagnoses malformed lists, duplicate keys and invalid WI IDs. The existing metadata reader retains its previous behavior; adding this declaration does not itself enable a new blocking gate.
16
+
17
+ For implementation writes permitted by a valid Full Mode session, the candidate runtime checks the session WI and its declared transitive dependencies and prints advisory reflection warnings to stderr. Existing session permission and exit 0 are retained, including when dependency information is incomplete or unreadable. Inception/product edits remain outside this additional check; `storyReflection.enabled: false` disables it. An omitted dependency declaration is reported as unverified, not silently interpreted as no dependencies.
18
+
19
+ The session advisory uses `paths.inceptionDocs` and `paths.designDocs` consistently for dependency discovery, design artifacts, reflection mappings, and legacy aliases. Existing mapping templates retain their canonical `docs/inception/` and `docs/product/construction/` prefixes; this session path resolves those prefixes to the configured roots. Product mappings outside construction, such as `docs/product/units/`, are unchanged. Invalid roots are reported as unverified rather than falling back to default directories. Older non-session reflection calls retain their previous behavior.
20
+
21
+ This checks configured reflection annotations, not semantic approval. A `fix` item needs reflection in at least one configured product candidate per affected unit; choosing the semantically relevant categories remains a review responsibility. Missing required design artifacts are reported as unverified in advisory mode.
22
+
23
+ For the authenticated Full Mode session path, v3 configuration may explicitly select `agentIntegration.preToolUse.dependencyReflection: "enforce"` (or `"advisory"`, the default behavior when omitted). No setting is added automatically. Enforce mode blocks known missing reflection and required artifacts; `storyReflection.enabled: false` still disables this check. Incomplete dependency information falls back to the existing Unit-wide reflection check and reports an unverified warning, even if that fallback passes. Read/configuration failures retain the existing session permission with an unverified warning. This is not a fail-closed semantic approval system.
24
+
25
+ Repair the indicated inception and product documents separately, obtain any required upstream decisions, and retry the implementation operation. WI inception Markdown design repair is no longer subjected to downstream implementation gates; protected-file checks, source-root overlaps, existing shared-plan/description gates, product gates, and implementation paths remain enforced. Do not treat adding a tag alone as design approval. Standard-gate CLI tests cover both unreflected existing product documents and missing product documents followed by repair and resumption.
26
+
3
27
  ## phasegate.config.json
4
28
 
5
29
  Place at project root. Generated by `npx phasegate init`.
@@ -666,9 +690,11 @@ Introduced in ISSUE-007 Wave 1 (v0.65.0) and wired into the pre-tool-use hook by
666
690
 
667
691
  Generate or refresh the snapshot with `npx phasegate baseline` (`--dry-run` to inspect, `--force` to overwrite, `--paths <glob,glob,...>` to scope, `--json` for CI-friendly output). See the [Baseline section in CLI Reference](cli-reference.md#baseline-retrofit-grandfather) for details.
668
692
 
669
- #### `agentIntegration` (Stop hook strict mode)
693
+ #### `agentIntegration` (Hook controls)
694
+
695
+ Controls agent-side hook behavior. In v3 configuration, optional `preToolUse.enabled` and `postToolUse.enabled` boolean values override the legacy `harnesses.agentLessonCollection` and `harnesses.cascadeUpdate` mappings respectively. When omitted, each legacy value is preserved (default `true` when that legacy key is also absent). No existing config is rewritten automatically. These controls do not remove protected-file or trust-root enforcement. <!-- @work-item-id WI-220 -->
670
696
 
671
- Controls how phasegate's agent-side hooks integrate with Claude Code. Currently only `stopHook.enforce` is exposed.
697
+ The independent keys require a version whose v3 schema supports them. Do not add them while older schema readers are still in use. Before downgrading, restore the configuration saved before adding these keys. There is currently no `config:plan` intent for these fields: use a human-reviewed, out-of-agent-hook edit under the trust-root policy rather than asking the agent to rewrite its own protection configuration.
672
698
 
673
699
  ```jsonc
674
700
  {
@@ -82,7 +82,7 @@ This separation is intentional:
82
82
  - **Post = "is what was written valid?"** — concerns the resulting code's quality.
83
83
  - **Stop = "is the session ready to end?"** — concerns the cumulative state across the session.
84
84
 
85
- If you expect L1 lint (e.g., missing `@unit` annotation) to **block** a Write before it happens, that is by design **not** the case. The PreToolUse hook intentionally does not run lint, because lint requires the resulting file content (which only exists after the write). Lint violations surface as **PostToolUse** decision JSON (`decision: "block"`) and trigger Claude Code to retry.
85
+ The PreToolUse hook does not run lint, because lint requires the resulting file content. The packaged `phasegate hook post-tool-use` command reports lint failures as advisory stderr with exit 0; it does not emit a block decision or request automatic retries. Separate shell hooks such as `analyze-errors-hook.sh` have their own output contract and are not changed by this packaged-command behavior. Commit and CI checks remain independent enforcement points.
86
86
 
87
87
  ### PreToolUse (before file write)
88
88
  - Enforces Phase Gate: blocks writing to source files if required design documents don't exist
@@ -114,6 +114,12 @@ Use /quick-implementor skill for version changes in package.json.
114
114
  - Runs Biome AST rules automatically
115
115
  - Provides immediate feedback on violations
116
116
 
117
+ <!-- @work-item-id WI-220 -->
118
+
119
+ The packaged command resolves direct-edit and complete patch targets relative to the payload's working directory, then runs lint from the project's configuration root. Multiple targets are retained. Analysis keeps the full dependency graph; only the reported diagnostics are scoped. Unknown shell commands and incomplete targets retain full lint rather than a potentially incomplete target list.
120
+
121
+ Read/Glob/Grep and disabled hooks finish silently without starting lint. The lint subprocess has a five-second limit; timeout is reported as **validation incomplete**, not a pass. It does not automatically retry. Run `phasegate lint` explicitly when ready to complete validation. This timeout covers the child lint process, not the host runtime's own hook timeout.
122
+
117
123
  ### Stop (before session end)
118
124
  - Runs `phasegate:complete-check` (L2-L4 full validation)
119
125
  - The built-in Stop hook runs the packaged PhaseGate CLI command; downstream projects do not need to provide `scripts/harness/cli/complete-check.ts`.
@@ -1,5 +1,8 @@
1
1
  # Skills Overview
2
2
 
3
+ <!-- @work-item-id WI-223 -->
4
+ For consumer projects, explicitly select `phasegate install --skills consumer --dry-run` (then `--apply` after review). This deploys 27 skills, excluding the Phasegate-maintainer-only `release-publisher` and `skill-creator`. Legacy `core` (7), `all` (29), and the omitted-option default (`all`) remain unchanged. `init` also accepts `consumer`. Reconcile preserves the set recorded in each skill root's `.harness-version`; missing/invalid/unknown metadata retains the legacy `all` fallback. Changing sets does not delete previously installed or user-owned skills. This is audience separation, not a claim of language-independent test tooling.
5
+
3
6
  Phasegate provides 29 skills covering the full AIDLC (AI-Driven Development Life Cycle). `npx phasegate init` and project `npx phasegate install` deploy skill bodies to root `skills/` and expose them to enabled agents through `.claude/skills/`, `.codex/skills/`, or `.agents/skills/` links. Personal install instead writes real local-only per-agent skill directories. <!-- @work-item-id WI-210 --> <!-- @work-item-id WI-385 -->
4
7
 
5
8
  Bundled `SKILL.md` files include `languages: [typescript]` frontmatter so PhaseGate can distinguish current TypeScript-oriented guidance from future language-specific skill variants. The metadata is advisory for applicability and does not prevent non-TypeScript projects from installing the catalog. <!-- @work-item-id WI-212 -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phasegate",
3
- "version": "0.340.0",
3
+ "version": "0.341.0",
4
4
  "packageManager": "pnpm@10.30.1",
5
5
  "description": "Phasegate — AI-agnostic quality defense toolkit. Enforces structural integrity between design intent and code.",
6
6
  "license": "MIT",
@@ -47,6 +47,7 @@
47
47
  "phasegate": "bin/phasegate"
48
48
  },
49
49
  "scripts": {
50
+ "pack:runtime": "node scripts/pack-runtime.mjs",
50
51
  "phasegate": "npx tsx scripts/harness/main.ts",
51
52
  "phasegate:status": "npx tsx scripts/harness/main.ts phasegate:status",
52
53
  "phasegate:enable": "npx tsx scripts/harness/main.ts enable-feature",
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @layer application
3
3
  * @unit agent-integration
4
+ * @work-item-id WI-220
4
5
  * @story H11-03
5
6
  *
6
7
  * HandlePostToolUseUseCase
@@ -39,6 +40,9 @@ export class HandlePostToolUseUseCase {
39
40
  }
40
41
 
41
42
  async execute(input: HandlePostToolUseInput): Promise<HandlePostToolUseOutput> {
43
+ if (input.affectedFilePaths.length === 0 && ['Read', 'Glob', 'Grep'].includes(input.toolName)) {
44
+ return { executed: false, skipReason: 'READ_ONLY' };
45
+ }
42
46
  const hookEvent = HookEvent.createPostToolUse(input.toolName, input.affectedFilePaths);
43
47
  const translationResult = await this.translator.translate(hookEvent);
44
48
 
@@ -27,6 +27,7 @@ import type {
27
27
  } from "../../domain/ports/full-mode-session-query-port.js";
28
28
  import type { PhaseGateQueryPort } from "../../domain/ports/phase-gate-query-port.js";
29
29
  import type { StoryReflectionQueryPort } from "../../domain/ports/story-reflection-query-port.js";
30
+ import { StoryReflectionQueryResult } from "../../domain/value-objects/story-reflection-query-result.js";
30
31
  import { AsyncHookToCliTranslator } from "../../domain/services/hook-to-cli-translator.js";
31
32
  import { HookEvent } from "../../domain/value-objects/hook-event.js";
32
33
  import type { BlockMetadata } from "../../domain/value-objects/hook-translation-result.js";
@@ -169,8 +170,15 @@ export class HandlePreToolUseUseCase {
169
170
  fullModeResult.dominantCategory,
170
171
  );
171
172
  if (sessionResult.allowed) {
173
+ const reflection = await this.checkSessionReflection(input, sessionResult);
174
+ if (reflection.sessionEnforced && !reflection.passed && !reflection.skipped) {
175
+ const blocked = HandlePreToolUseUseCase.buildStoryReflectionBlockOutput(input.targetFilePaths[0], reflection.blockers, reflection.warnings);
176
+ return { ...blocked, error: { message: `[L2-STORY-REFLECTION] 明示設定による依存反映チェック\n${reflection.blockers.join('\n')}\n修正方法: inception/productを単独で編集し、必要な上位判断と設計内容を反映してから同じ操作を再評価してください。タグだけでは意味的承認を証明しません。\n依存不明の場合はdescription.mdのID・所属・depends_onを確認してください。` } };
177
+ }
178
+ const warnings = [...reflection.blockers, ...reflection.warnings];
172
179
  return {
173
180
  shouldBlock: false,
181
+ ...(warnings.length > 0 ? { storyReflectionWarnings: warnings } : {}),
174
182
  fullModeSessionAllowed: {
175
183
  workItemId: sessionResult.workItemId,
176
184
  unit: sessionResult.unit,
@@ -187,7 +195,7 @@ export class HandlePreToolUseUseCase {
187
195
  input.targetFilePaths[0],
188
196
  fullModeResult,
189
197
  guidance,
190
- unitIdForGuidance,
198
+ this.deriveRecoveryUnitId(input.targetFilePaths),
191
199
  sessionResult,
192
200
  );
193
201
  }
@@ -225,6 +233,19 @@ export class HandlePreToolUseUseCase {
225
233
  );
226
234
  }
227
235
 
236
+ private async checkSessionReflection(
237
+ input: HandlePreToolUseInput,
238
+ session: FullModeSessionQueryResult,
239
+ ): Promise<StoryReflectionQueryResult> {
240
+ if (!session.active || !session.unit || !session.workItemId ||
241
+ !this.storyReflectionQueryPort?.checkSessionReflection || !this.resolveStoryReflectionScope(input)) return StoryReflectionQueryResult.skipped();
242
+ try {
243
+ return await this.storyReflectionQueryPort.checkSessionReflection(session.unit, session.workItemId);
244
+ } catch (error) {
245
+ return StoryReflectionQueryResult.skipped([`WI依存反映は未検証です(既存sessionの許可を維持): ${error instanceof Error ? error.message : String(error)}`]);
246
+ }
247
+ }
248
+
228
249
  private async checkGrandfather(targetFilePaths: readonly string[]): Promise<BaselineGrandfatherCheckResult> {
229
250
  if (this.baselineGrandfatherQueryPort === undefined) {
230
251
  return {
@@ -352,6 +373,7 @@ export class HandlePreToolUseUseCase {
352
373
  HandlePreToolUseUseCase.appendJudgmentContextLines(lines, sessionResult);
353
374
  const suggestedSkill = guidance?.suggestedSkill ?? "/story-implementor";
354
375
  lines.push(`次のアクション: ${suggestedSkill} スキルを使用して設計フェーズから開始してください。`);
376
+ lines.push(' inceptionの計画編集と実装を分け、実装は対象Unitごとに設計反映とsession開始を行ってください。');
355
377
  if (unitId !== undefined && unitId !== "") {
356
378
  if (HandlePreToolUseUseCase.isSessionActiveButRejected(sessionResult)) {
357
379
  lines.push(
@@ -362,6 +384,8 @@ export class HandlePreToolUseUseCase {
362
384
  ` 実装フェーズ開始時: phasegate session begin --mode full --unit ${unitId} --work-item <WI-XXX> --reason "<reason>" --duration 1h`,
363
385
  );
364
386
  lines.push(" 実装完了時: phasegate session end --work-item <WI-XXX>");
387
+ } else {
388
+ lines.push(' 対象Unitが一意に決まりません。実装対象パスのUnitを確認してください(_crossはsessionのUnitに指定できません)。');
365
389
  }
366
390
  HandlePreToolUseUseCase.appendGuidanceLines(lines, guidance, unitId);
367
391
 
@@ -458,6 +482,17 @@ export class HandlePreToolUseUseCase {
458
482
  return `${templatePath}(未配置なら: npx phasegate skills info ${skillMatch[1]})`;
459
483
  }
460
484
 
485
+ private deriveRecoveryUnitId(targetFilePaths: readonly string[]): string | undefined {
486
+ const projectPaths = this.configQueryPort.getProjectPaths();
487
+ const units = new Set<string>();
488
+ for (const targetFilePath of targetFilePaths) {
489
+ if (HandlePreToolUseUseCase.isUnderInception(targetFilePath, projectPaths.getDocsInception())) continue;
490
+ const unit = WriteTargetScope.fromPath(targetFilePath, projectPaths)?.unitId;
491
+ if (unit !== undefined && /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(unit)) units.add(unit);
492
+ }
493
+ return units.size === 1 ? [...units][0] : undefined;
494
+ }
495
+
461
496
  private deriveUnitIdFromPaths(targetFilePaths: readonly string[]): string | undefined {
462
497
  const projectPaths = this.configQueryPort.getProjectPaths();
463
498
  for (const targetFilePath of targetFilePaths) {
@@ -2,14 +2,12 @@
2
2
  * @layer application
3
3
  * @unit agent-integration
4
4
  * @story H11-04
5
+ * @work-item-id WI-220
5
6
  *
6
7
  * HandleStopUseCase
7
8
  * Stop Hook処理のオーケストレーション。ReentryGuardライフサイクル管理の唯一の制御点
8
9
  */
9
10
 
10
- import { HookEvent } from '../../domain/value-objects/hook-event.js';
11
- import { ReentryGuard } from '../../domain/entities/reentry-guard.js';
12
- import { AsyncHookToCliTranslator } from '../../domain/services/hook-to-cli-translator.js';
13
11
  import type { ReentryGuardStatePort } from '../../domain/ports/reentry-guard-state-port.js';
14
12
  import type { ConfigQueryPort } from '../../domain/ports/config-query-port.js';
15
13
  import type { CliExecutorPort } from '../ports/cli-executor-port.js';
@@ -19,7 +17,8 @@ export interface HandleStopUseCasePorts {
19
17
  reentryGuardStatePort: ReentryGuardStatePort;
20
18
  cliExecutorPort: CliExecutorPort;
21
19
  configQueryPort: ConfigQueryPort;
22
- cliCommandRegistryPort: {
20
+ /** Legacy constructor input; Stop dispatches complete-check directly. */
21
+ cliCommandRegistryPort?: {
23
22
  hasCommand(commandName: string): Promise<boolean>;
24
23
  listCommands(): Promise<readonly string[]>;
25
24
  };
@@ -37,16 +36,11 @@ export class HandleStopUseCase {
37
36
  private readonly reentryGuardStatePort: ReentryGuardStatePort;
38
37
  private readonly cliExecutorPort: CliExecutorPort;
39
38
  private readonly configQueryPort: ConfigQueryPort;
40
- private readonly cliCommandRegistryPort: {
41
- hasCommand(commandName: string): Promise<boolean>;
42
- listCommands(): Promise<readonly string[]>;
43
- };
44
39
 
45
40
  constructor(ports: HandleStopUseCasePorts) {
46
41
  this.reentryGuardStatePort = ports.reentryGuardStatePort;
47
42
  this.cliExecutorPort = ports.cliExecutorPort;
48
43
  this.configQueryPort = ports.configQueryPort;
49
- this.cliCommandRegistryPort = ports.cliCommandRegistryPort;
50
44
  }
51
45
 
52
46
  async execute(input: HandleStopInput): Promise<HandleStopOutput> {
@@ -5,4 +5,6 @@ import type { StoryReflectionQueryResult } from '../value-objects/story-reflecti
5
5
 
6
6
  export interface StoryReflectionQueryPort {
7
7
  checkReflection(unitId: string): Promise<StoryReflectionQueryResult>;
8
+ /** WI-220: optional session path, advisory by default; caller supplies authenticated identity. */
9
+ checkSessionReflection?(unitId: string, workItemId: string): Promise<StoryReflectionQueryResult>;
8
10
  }
@@ -140,7 +140,7 @@ export class HookToCliTranslator {
140
140
  throw new CommandNotRegisteredError(commandName);
141
141
  }
142
142
 
143
- return HookTranslationResult.execute(commandName, ['--fast'], 0, 500);
143
+ return HookTranslationResult.execute(commandName, [...new Set(event.affectedFilePaths)].flatMap((target) => ['--target', target]), 0, 5000);
144
144
  }
145
145
 
146
146
  private translateStop(_event: StopEvent): HookTranslationResult {
@@ -248,7 +248,10 @@ export class AsyncHookToCliTranslator {
248
248
  const projectPaths = await (this.configQueryPort as ConfigQueryPort & {
249
249
  getProjectPaths(): unknown;
250
250
  }).getProjectPaths();
251
- const detectedScope = event.targetFilePaths
251
+ const gatePaths = event.targetFilePaths.filter(filePath => !WriteTargetScope.isInceptionDocument(
252
+ filePath, projectPaths as Parameters<typeof WriteTargetScope.fromPath>[1],
253
+ ));
254
+ const detectedScope = gatePaths
252
255
  .map((filePath) => WriteTargetScope.fromPath(filePath, projectPaths as Parameters<typeof WriteTargetScope.fromPath>[1]))
253
256
  .find((scope): scope is WriteTargetScope => scope !== null);
254
257
 
@@ -272,7 +275,7 @@ export class AsyncHookToCliTranslator {
272
275
 
273
276
  const phaseGateResult = await this.phaseGateQueryPort.checkGate(
274
277
  detectedScope,
275
- event.targetFilePaths[0],
278
+ gatePaths.find(filePath => WriteTargetScope.fromPath(filePath, projectPaths as Parameters<typeof WriteTargetScope.fromPath>[1]) !== null),
276
279
  );
277
280
  if (!phaseGateResult.hasPassed()) {
278
281
  return HookTranslationResult.block({
@@ -292,7 +295,7 @@ export class AsyncHookToCliTranslator {
292
295
  });
293
296
  }
294
297
 
295
- private async translatePostToolUse(_event: PostToolUseEvent): Promise<HookTranslationResult> {
298
+ private async translatePostToolUse(event: PostToolUseEvent): Promise<HookTranslationResult> {
296
299
  const isEnabled = await this.configQueryPort.isHookEnabled('post-tool-use');
297
300
  if (!isEnabled) {
298
301
  return HookTranslationResult.skip('HOOK_DISABLED');
@@ -304,7 +307,7 @@ export class AsyncHookToCliTranslator {
304
307
  throw new CommandNotRegisteredError(commandName);
305
308
  }
306
309
 
307
- return HookTranslationResult.execute(commandName, ['--fast'], 0, 500);
310
+ return HookTranslationResult.execute(commandName, [...new Set(event.affectedFilePaths)].flatMap((target) => ['--target', target]), 0, 5000);
308
311
  }
309
312
 
310
313
  private async translateStop(_event: StopEvent): Promise<HookTranslationResult> {
@@ -6,7 +6,7 @@
6
6
  * HookEvent → CLI実行指示への変換結果
7
7
  */
8
8
 
9
- export type SkipReason = 'REENTRY_DETECTED' | 'HOOK_DISABLED' | 'TIMEOUT_EXCEEDED';
9
+ export type SkipReason = 'REENTRY_DETECTED' | 'HOOK_DISABLED' | 'TIMEOUT_EXCEEDED' | 'READ_ONLY';
10
10
 
11
11
  export type BlockReason = 'PROTECTED_FILE' | 'PHASE_GATE' | 'FULL_MODE_REQUIRED';
12
12
 
@@ -14,6 +14,7 @@ export class StoryReflectionQueryResult {
14
14
  readonly blockers: readonly string[];
15
15
  readonly warnings: readonly string[];
16
16
  readonly skipped: boolean;
17
+ declare readonly sessionEnforced?: true;
17
18
 
18
19
  private constructor(
19
20
  passed: boolean,
@@ -27,8 +28,8 @@ export class StoryReflectionQueryResult {
27
28
  this.skipped = skipped;
28
29
  }
29
30
 
30
- static pass(): StoryReflectionQueryResult {
31
- return new StoryReflectionQueryResult(true, [], [], false);
31
+ static pass(warnings: string[] = []): StoryReflectionQueryResult {
32
+ return new StoryReflectionQueryResult(true, [], warnings, false);
32
33
  }
33
34
 
34
35
  static skipped(warnings: string[] = []): StoryReflectionQueryResult {
@@ -45,6 +46,13 @@ export class StoryReflectionQueryResult {
45
46
  return new StoryReflectionQueryResult(false, blockers, warnings, false);
46
47
  }
47
48
 
49
+ /** Only trusted configuration readers may mark the optional session enforcement path. */
50
+ withSessionEnforcement(): StoryReflectionQueryResult {
51
+ return Object.assign(new StoryReflectionQueryResult(this.passed, this.blockers, this.warnings, this.skipped), {
52
+ sessionEnforced: true as const,
53
+ });
54
+ }
55
+
48
56
  hasPassed(): boolean {
49
57
  return this.passed;
50
58
  }
@@ -61,6 +61,9 @@ export class WriteTargetScope {
61
61
  if (sourceMatch !== null) {
62
62
  const [unitId] = sourceMatch;
63
63
  if (unitId !== undefined) {
64
+ // A source-root file (e.g. main.ts) is not a Unit directory.
65
+ // Keep nested paths, including dotted directory names, under the gate.
66
+ if (sourceMatch.length === 1 && unitId.includes('.')) continue;
64
67
  return WriteTargetScope.create({ level: 3, unitId });
65
68
  }
66
69
  }
@@ -124,6 +127,15 @@ export class WriteTargetScope {
124
127
  return null;
125
128
  }
126
129
 
130
+ /** Phase-1 Markdown repair is not implementation; source-root overlaps remain gated. */
131
+ static isInceptionDocument(filePath: string, projectPaths: ProjectPaths): boolean {
132
+ const normalized = normalize(filePath);
133
+ return normalized.toLowerCase().endsWith('.md')
134
+ && matchPrefix(normalized, projectPaths.getDocsInception()) !== null
135
+ && !projectPaths.getSource().some(root => matchPrefix(normalized, root) !== null)
136
+ && WriteTargetScope.fromPath(normalized, projectPaths)?.level === 3;
137
+ }
138
+
127
139
  equals(other: WriteTargetScope): boolean {
128
140
  return this.level === other.level && this.unitId === other.unitId && this.storyId === other.storyId;
129
141
  }
@@ -7,38 +7,47 @@
7
7
  * CliExecutorPort の実装。子プロセスで CLI コマンドを実行する
8
8
  */
9
9
 
10
- import { spawn } from 'node:child_process';
10
+ import { spawn, spawnSync } from 'node:child_process';
11
+ import { statSync } from 'node:fs';
12
+ import { createRequire } from 'node:module';
11
13
  import { dirname, resolve } from 'node:path';
12
14
  import { fileURLToPath } from 'node:url';
13
15
  import type { CliExecutorPort, CliExecutionResult } from '../../application/ports/cli-executor-port.js';
14
16
  import { TimeoutError } from '../../application/ports/cli-executor-port.js';
15
17
 
18
+ const tsxCliPath = createRequire(import.meta.url).resolve('tsx/cli');
19
+
16
20
  function getHarnessMainPath(): string {
17
21
  return resolve(dirname(fileURLToPath(import.meta.url)), '../../../main.ts');
18
22
  }
19
23
 
20
24
  /**
21
25
  * CommandName を実行可能なコマンドに変換する
22
- * 例: 'phasegate:lint' → ['npx', 'tsx', '<package>/scripts/harness/main.ts', 'phasegate:lint']
26
+ * 例: 'phasegate:lint' → [node, '<package>/tsx/cli', '<package>/scripts/harness/main.ts', 'phasegate:lint']
23
27
  * テスト時は直接スクリプトパスで execute を呼ぶことも可能
24
28
  */
25
29
  function resolveCommand(commandName: string): { cmd: string; args: string[] } {
26
30
  if (commandName.startsWith('phasegate:')) {
31
+ const compiledMain = resolve(dirname(getHarnessMainPath()), 'main.js');
32
+ let compiled = false;
33
+ try { compiled = statSync(compiledMain).isFile(); } catch { /* Source-only packages retain the TS entry. */ }
27
34
  return {
28
- cmd: 'npx',
29
- args: ['tsx', getHarnessMainPath(), commandName],
35
+ cmd: process.execPath,
36
+ args: compiled ? [compiledMain, commandName] : [tsxCliPath, getHarnessMainPath(), commandName],
30
37
  };
31
38
  }
32
39
 
33
40
  // Legacy extension commands may still be provided as project-local wrappers.
34
41
  const slug = commandName.replace('phasegate:', '');
35
42
  return {
36
- cmd: 'npx',
37
- args: ['tsx', `scripts/harness/cli/${slug}.ts`],
43
+ cmd: process.execPath,
44
+ args: [tsxCliPath, `scripts/harness/cli/${slug}.ts`],
38
45
  };
39
46
  }
40
47
 
41
48
  export class ChildProcessCliExecutorAdapter implements CliExecutorPort {
49
+ constructor(private readonly options: { cwd?: string } = {}) {}
50
+
42
51
  async execute(
43
52
  command: string,
44
53
  args: string[],
@@ -49,9 +58,9 @@ export class ChildProcessCliExecutorAdapter implements CliExecutorPort {
49
58
  let spawnArgs: string[];
50
59
 
51
60
  // If the command looks like a file path (contains / or .ts), run it directly
52
- if (command.includes('/') || command.endsWith('.ts')) {
53
- cmd = 'npx';
54
- spawnArgs = ['tsx', command, ...args];
61
+ if (command.includes('/') || command.includes('\\') || command.endsWith('.ts')) {
62
+ cmd = process.execPath;
63
+ spawnArgs = [tsxCliPath, command, ...args];
55
64
  } else {
56
65
  const resolved = resolveCommand(command);
57
66
  cmd = resolved.cmd;
@@ -61,11 +70,16 @@ export class ChildProcessCliExecutorAdapter implements CliExecutorPort {
61
70
  let stdout = '';
62
71
  let stderr = '';
63
72
  let timedOut = false;
73
+ let closed = false;
74
+ let cleanupDone = false;
64
75
 
65
76
  const child = spawn(cmd, spawnArgs, {
66
77
  stdio: ['pipe', 'pipe', 'pipe'],
67
78
  shell: false,
79
+ cwd: this.options.cwd,
80
+ detached: process.platform !== 'win32',
68
81
  });
82
+ child.stdin?.end();
69
83
 
70
84
  child.stdout?.on('data', (data: Buffer) => {
71
85
  stdout += data.toString();
@@ -76,21 +90,57 @@ export class ChildProcessCliExecutorAdapter implements CliExecutorPort {
76
90
  });
77
91
 
78
92
  let timer: NodeJS.Timeout | undefined;
93
+ let killTimer: NodeJS.Timeout | undefined;
94
+ const finishTimeout = () => {
95
+ if (closed && cleanupDone) reject(new TimeoutError(command, timeoutMs!));
96
+ };
97
+ const signalTree = (signal: NodeJS.Signals) => {
98
+ if (child.pid === undefined) return;
99
+ try {
100
+ process.kill(-child.pid, signal);
101
+ } catch (error) {
102
+ if ((error as NodeJS.ErrnoException).code !== 'ESRCH') throw error;
103
+ }
104
+ };
79
105
 
80
106
  if (timeoutMs !== undefined) {
81
107
  timer = setTimeout(() => {
82
108
  timedOut = true;
83
- child.kill('SIGTERM');
84
- reject(new TimeoutError(command, timeoutMs));
109
+ try {
110
+ if (process.platform === 'win32' && child.pid !== undefined) {
111
+ const cleanup = spawnSync('taskkill', ['/PID', String(child.pid), '/T', '/F'], { shell: false, timeout: 5000 });
112
+ if (cleanup.error || cleanup.status !== 0) throw cleanup.error ?? new Error(`Process tree cleanup failed: ${cleanup.status}`);
113
+ cleanupDone = true;
114
+ finishTimeout();
115
+ } else {
116
+ signalTree('SIGTERM');
117
+ // Keep the group cleanup even if its leader closes before descendants.
118
+ killTimer = setTimeout(() => {
119
+ try {
120
+ signalTree('SIGKILL');
121
+ cleanupDone = true;
122
+ finishTimeout();
123
+ } catch (error) {
124
+ reject(error);
125
+ }
126
+ }, 250);
127
+ }
128
+ } catch (error) {
129
+ reject(error);
130
+ }
85
131
  }, timeoutMs);
86
132
  }
87
133
 
88
134
  child.on('close', (exitCode) => {
135
+ closed = true;
89
136
  if (timer) clearTimeout(timer);
90
- if (timedOut) return;
137
+ if (timedOut) {
138
+ finishTimeout();
139
+ return;
140
+ }
91
141
 
92
142
  resolve({
93
- exitCode: exitCode ?? 0,
143
+ exitCode: exitCode ?? 1,
94
144
  stdout,
95
145
  stderr,
96
146
  timedOut: false,
@@ -99,6 +149,7 @@ export class ChildProcessCliExecutorAdapter implements CliExecutorPort {
99
149
 
100
150
  child.on('error', (error) => {
101
151
  if (timer) clearTimeout(timer);
152
+ if (killTimer) clearTimeout(killTimer);
102
153
  reject(error);
103
154
  });
104
155
  });