@boyingliu01/xp-gate 0.35.24 → 0.36.1

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 (70) hide show
  1. package/adapter-common.sh +55 -2
  2. package/adapters/csharp.sh +245 -0
  3. package/adapters/gate-3.sh +1 -1
  4. package/adapters/gate-4.sh +1 -1
  5. package/adapters/gate-9.sh +37 -2
  6. package/adapters/iac.sh +45 -5
  7. package/bin/xp-gate.js +18 -11
  8. package/gate-10.sh +18 -3
  9. package/gate-13-compiler-strictness.sh +181 -0
  10. package/gate-3.sh +1 -1
  11. package/gate-4.sh +1 -1
  12. package/gate-9.sh +37 -2
  13. package/gates/common.ts +9 -2
  14. package/hooks/adapter-common.sh +55 -2
  15. package/hooks/gate-10.sh +18 -3
  16. package/hooks/gate-13-compiler-strictness.sh +181 -0
  17. package/hooks/gate-3.sh +1 -1
  18. package/hooks/gate-4.sh +1 -1
  19. package/hooks/gate-9.sh +37 -2
  20. package/hooks/gate-registry.sh +161 -0
  21. package/hooks/lib/test-failure.sh +29 -4
  22. package/hooks/pre-commit +122 -3
  23. package/lib/__tests__/agent-runtime-journal.test.js +42 -18
  24. package/lib/__tests__/check-single-file.test.js +55 -4
  25. package/lib/__tests__/detect-deps-dsh-profile.test.js +43 -0
  26. package/lib/__tests__/doctor-delphi.test.js +175 -0
  27. package/lib/__tests__/install-skill-full-dir.test.js +45 -2
  28. package/lib/__tests__/install-skill.test.js +84 -3
  29. package/lib/__tests__/mirror-sync.test.js +23 -5
  30. package/lib/__tests__/principles-csharp-coverage.test.js +227 -0
  31. package/lib/__tests__/uninstall-global-residue.test.js +192 -28
  32. package/lib/__tests__/update-skill-flags.test.js +78 -0
  33. package/lib/__tests__/update-skill.test.js +156 -3
  34. package/lib/detect-deps.js +24 -0
  35. package/lib/doctor.js +80 -12
  36. package/lib/install-skill.js +67 -18
  37. package/lib/mattpocock-license-verdict.json +1 -1
  38. package/lib/principles.js +9 -1
  39. package/lib/uninstall.js +96 -2
  40. package/lib/update-skill.js +143 -2
  41. package/mock-policy/AGENTS.md +4 -4
  42. package/mutation/AGENTS.md +4 -4
  43. package/package.json +1 -1
  44. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  45. package/plugins/claude-code/skills/delphi-review/AGENTS.md +4 -4
  46. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
  47. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
  48. package/plugins/dsh/lib/index.d.ts.map +1 -1
  49. package/plugins/dsh/lib/index.js +7 -4
  50. package/plugins/dsh/package.json +1 -1
  51. package/plugins/dsh/skills/delphi-review/AGENTS.md +4 -4
  52. package/plugins/dsh/skills/sprint-flow/AGENTS.md +4 -4
  53. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +4 -4
  54. package/plugins/dsh/src/index.test.ts +25 -0
  55. package/plugins/dsh/src/index.ts +7 -4
  56. package/plugins/opencode/package.json +1 -1
  57. package/plugins/opencode/skills/delphi-review/AGENTS.md +4 -4
  58. package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
  59. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
  60. package/plugins/qoder/plugin.json +1 -1
  61. package/plugins/qoder/skills/delphi-review/AGENTS.md +4 -4
  62. package/plugins/qoder/skills/sprint-flow/AGENTS.md +4 -4
  63. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
  64. package/principles/AGENTS.md +4 -4
  65. package/principles/adapters/csharp.ts +309 -0
  66. package/principles/analyzer.ts +5 -0
  67. package/scripts/delphi-external-review.cjs +130 -101
  68. package/skills/delphi-review/AGENTS.md +4 -4
  69. package/skills/sprint-flow/AGENTS.md +4 -4
  70. package/skills/test-specification-alignment/AGENTS.md +4 -4
@@ -659,10 +659,129 @@ async function callExpertWithSchemaRetry({ provider, model, systemPrompt, userPr
659
659
  }
660
660
 
661
661
  // ── API call ───────────────────────────────────────────────────────────
662
- async function callModelAPI(providerConfig, model, systemPrompt, userPrompt, options = {}) {
663
- const url = `${providerConfig.base_url.replace(/\/$/, '')}/chat/completions`;
664
662
 
665
- const body = {
663
+ /**
664
+ * Map a 429 to a verdict. Not every 429 is a transient rate limit — WhaleCloud
665
+ * returns `type: "budget_exceeded"` in two distinct non-retryable cases, and a
666
+ * blind retry cannot clear either and would burn two round-trips before failing:
667
+ * - `error.param === "category_quota"`: the account has no ECONOMY/STANDARD
668
+ * quota configured for the model ("需要专项额度...尚未配置") — needs an admin.
669
+ * - `error.param === "cost_quota"`: the account's daily spending cap is
670
+ * exhausted ("今日额度已耗尽...限额: 100.00元") — resets the next day.
671
+ * Surface which kind it is so the caller knows whether to wait or to ask for an
672
+ * admin change, rather than treating both as a generic rate limit.
673
+ *
674
+ * Extracted from `callModelAPI` so the branch reads as one rule (#722 follow-up:
675
+ * the enclosing function was CCN 33 against a block threshold of 10).
676
+ */
677
+ async function classifyRateLimitResponse(response) {
678
+ let quotaType = '';
679
+ try {
680
+ const body = await response.json();
681
+ if (body && body.error && body.error.type === 'budget_exceeded') {
682
+ quotaType = body.error.param === 'cost_quota' ? 'daily budget exhausted' : 'quota not configured';
683
+ }
684
+ } catch {
685
+ // body is not JSON; fall through to the generic rate-limit handling
686
+ }
687
+ if (quotaType) {
688
+ return { error: true, retryable: false, message: `WhaleCloud budget_exceeded: ${quotaType} for this model.` };
689
+ }
690
+ return { error: true, retryable: true, message: 'Rate limit exceeded (429).' };
691
+ }
692
+
693
+ /**
694
+ * Map a 5xx to a verdict, reading the provider's structured error body the same
695
+ * way the 429 branch does instead of discarding it (#705).
696
+ *
697
+ * Measured on the whalecloud gateway: a prompt containing
698
+ * `new ProcessBuilder(args).start()` makes the gateway fail in its
699
+ * response-processing stage and return 500 with
700
+ * {"error":{"type":"response_processing_error",
701
+ * "param":"non_stream_chat_response","code":"500"}}
702
+ * The old code threw that away and reported "Server error (500).", which reads
703
+ * as a transient outage. `type`/`param` are what tell an operator apart gateway
704
+ * flapping vs. upstream timeout vs. content-triggered processing failure —
705
+ * three problems with three different responses.
706
+ *
707
+ * Retryability is the other half: a content-dependent failure cannot clear by
708
+ * retrying (the content did not change), so marking it retryable burns the
709
+ * orchestrator's retry budget on a guaranteed repeat. Only an explicitly
710
+ * recognised response_processing_error is demoted; every other 5xx keeps the
711
+ * previous retryable=true, and an unparseable body keeps the previous behaviour
712
+ * entirely — reading the body must not change the verdict on its own.
713
+ */
714
+ async function classifyServerErrorResponse(response) {
715
+ let detail = '';
716
+ let processingError = false;
717
+ try {
718
+ const body = await response.json();
719
+ if (body && body.error && typeof body.error === 'object') {
720
+ const type = typeof body.error.type === 'string' ? body.error.type : '';
721
+ const param = typeof body.error.param === 'string' ? body.error.param : '';
722
+ if (type === 'response_processing_error') {
723
+ processingError = true;
724
+ }
725
+ const parts = [type, param].filter(Boolean);
726
+ if (parts.length > 0) {
727
+ detail = ` Provider reported: ${parts.join('/')}.`;
728
+ }
729
+ }
730
+ } catch {
731
+ // body is not JSON; keep the previous behaviour untouched
732
+ }
733
+ if (processingError) {
734
+ return {
735
+ error: true,
736
+ retryable: false,
737
+ message: `Server error (${response.status}).${detail} This is a content-dependent upstream processing failure, so retrying the same prompt will fail the same way.`,
738
+ };
739
+ }
740
+ return { error: true, retryable: true, message: `Server error (${response.status}).${detail}` };
741
+ }
742
+
743
+ /**
744
+ * Map a 2xx response body to a verdict.
745
+ *
746
+ * The gateway must echo which model actually served the request. Writing `null`
747
+ * here used to be tolerated downstream, which let a run that never resolved a
748
+ * model count as a successful expert call (#423). Fail at the source instead,
749
+ * so the evidence never records an unverifiable expert.
750
+ */
751
+ async function readSuccessfulResponse(response) {
752
+ const data = await response.json();
753
+ if (!isPlainObject(data) || !Array.isArray(data.choices)) {
754
+ return { error: true, message: 'Invalid response from model.' };
755
+ }
756
+ const firstChoice = data.choices[0];
757
+ const message = isPlainObject(firstChoice) ? firstChoice.message : null;
758
+ const content = isPlainObject(message) ? message.content : null;
759
+ if (typeof content !== 'string' || content.trim() === '') {
760
+ return { error: true, message: 'Invalid response from model.' };
761
+ }
762
+
763
+ const resolvedModel = typeof data.model === 'string' ? data.model.trim() : '';
764
+ if (resolvedModel === '') {
765
+ return {
766
+ error: true,
767
+ message: 'Gateway response did not include a resolved model id; cannot prove which model ran.',
768
+ };
769
+ }
770
+
771
+ return {
772
+ success: true,
773
+ content: content.trim(),
774
+ resolved_model: resolvedModel,
775
+ };
776
+ }
777
+
778
+ /**
779
+ * Build the request body. `temperature: 0.3` is a Delphi-review choice: low
780
+ * enough to keep verdicts reproducible, non-zero so three experts on the same
781
+ * model are not literally identical runs.
782
+ */
783
+ function buildChatBody(model, systemPrompt, userPrompt) {
784
+ return {
666
785
  model: model,
667
786
  messages: [
668
787
  { role: 'system', content: systemPrompt },
@@ -671,6 +790,11 @@ async function callModelAPI(providerConfig, model, systemPrompt, userPrompt, opt
671
790
  temperature: 0.3,
672
791
  response_format: { type: 'json_object' },
673
792
  };
793
+ }
794
+
795
+ async function callModelAPI(providerConfig, model, systemPrompt, userPrompt, options = {}) {
796
+ const url = `${providerConfig.base_url.replace(/\/$/, '')}/chat/completions`;
797
+ const body = buildChatBody(model, systemPrompt, userPrompt);
674
798
 
675
799
  // Same constant as the runner default: a second literal here silently
676
800
  // diverges whenever the default is retuned (#429).
@@ -696,113 +820,18 @@ async function callModelAPI(providerConfig, model, systemPrompt, userPrompt, opt
696
820
  }
697
821
 
698
822
  if (response.status === 429) {
699
- // A 429 is not always a transient rate limit. WhaleCloud returns 429 with
700
- // `type: "budget_exceeded"` in two distinct non-retryable cases, which a
701
- // blind retry cannot clear and would otherwise burn two round-trips before
702
- // failing:
703
- // - `error.param === "category_quota"`: the account has no ECONOMY/STANDARD
704
- // quota configured for the model ("需要专项额度...尚未配置") — needs an
705
- // admin to add it.
706
- // - `error.param === "cost_quota"`: the account's daily spending cap is
707
- // exhausted ("今日额度已耗尽...限额: 100.00元") — resets the next day.
708
- // Surface which kind it is so the caller knows whether to wait or to request
709
- // an admin change, rather than treating both as a generic rate limit.
710
- let quotaType = '';
711
- try {
712
- const body = await response.json();
713
- if (body && body.error && body.error.type === 'budget_exceeded') {
714
- quotaType = body.error.param === 'cost_quota' ? 'daily budget exhausted' : 'quota not configured';
715
- }
716
- } catch {
717
- // body is not JSON; fall through to the generic rate-limit handling
718
- }
719
- if (quotaType) {
720
- return { error: true, retryable: false, message: `WhaleCloud budget_exceeded: ${quotaType} for this model.` };
721
- }
722
- return { error: true, retryable: true, message: 'Rate limit exceeded (429).' };
823
+ return await classifyRateLimitResponse(response);
723
824
  }
724
825
 
725
826
  if (response.status >= 500) {
726
- // (#705) Read the provider's structured error body, the same way the 429
727
- // branch above does, instead of discarding it.
728
- //
729
- // Measured on the whalecloud gateway: a prompt containing
730
- // `new ProcessBuilder(args).start()` makes the gateway fail in its
731
- // response-processing stage and return 500 with
732
- // {"error":{"type":"response_processing_error",
733
- // "param":"non_stream_chat_response","code":"500"}}
734
- // The old code threw that away and reported "Server error (500).", which
735
- // reads as a transient outage. `type`/`param` are what tell an operator
736
- // apart gateway flapping vs. upstream timeout vs. content-triggered
737
- // processing failure — three problems with three different responses.
738
- //
739
- // Retryability is the other half: a content-dependent failure cannot clear
740
- // by retrying (the content did not change), so marking it retryable burns
741
- // the orchestrator's retry budget on a guaranteed repeat. Only an
742
- // explicitly recognised response_processing_error is demoted; every other
743
- // 5xx keeps the previous retryable=true, and an unparseable body keeps the
744
- // previous behaviour entirely — reading the body must not change the
745
- // verdict on its own.
746
- let detail = '';
747
- let processingError = false;
748
- try {
749
- const body = await response.json();
750
- if (body && body.error && typeof body.error === 'object') {
751
- const type = typeof body.error.type === 'string' ? body.error.type : '';
752
- const param = typeof body.error.param === 'string' ? body.error.param : '';
753
- if (type === 'response_processing_error') {
754
- processingError = true;
755
- }
756
- const parts = [type, param].filter(Boolean);
757
- if (parts.length > 0) {
758
- detail = ` Provider reported: ${parts.join('/')}.`;
759
- }
760
- }
761
- } catch {
762
- // body is not JSON; keep the previous behaviour untouched
763
- }
764
- if (processingError) {
765
- return {
766
- error: true,
767
- retryable: false,
768
- message: `Server error (${response.status}).${detail} This is a content-dependent upstream processing failure, so retrying the same prompt will fail the same way.`,
769
- };
770
- }
771
- return { error: true, retryable: true, message: `Server error (${response.status}).${detail}` };
827
+ return await classifyServerErrorResponse(response);
772
828
  }
773
829
 
774
830
  if (!response.ok) {
775
831
  return { error: true, message: `API request failed (${response.status}). Provider response was not included.` };
776
832
  }
777
833
 
778
- const data = await response.json();
779
- if (!isPlainObject(data) || !Array.isArray(data.choices)) {
780
- return { error: true, message: 'Invalid response from model.' };
781
- }
782
- const firstChoice = data.choices[0];
783
- const message = isPlainObject(firstChoice) ? firstChoice.message : null;
784
- const content = isPlainObject(message) ? message.content : null;
785
- if (typeof content !== 'string' || content.trim() === '') {
786
- return { error: true, message: 'Invalid response from model.' };
787
- }
788
-
789
- // The gateway must echo which model actually served the request. Writing
790
- // `null` here used to be tolerated downstream, which let a run that never
791
- // resolved a model count as a successful expert call (#423). Fail at the
792
- // source instead, so the evidence never records an unverifiable expert.
793
- const resolvedModel = typeof data.model === 'string' ? data.model.trim() : '';
794
- if (resolvedModel === '') {
795
- return {
796
- error: true,
797
- message: 'Gateway response did not include a resolved model id; cannot prove which model ran.',
798
- };
799
- }
800
-
801
- return {
802
- success: true,
803
- content: content.trim(),
804
- resolved_model: resolvedModel,
805
- };
834
+ return await readSuccessfulResponse(response);
806
835
  } catch (err) {
807
836
  if (err instanceof Error && err.name === 'AbortError') {
808
837
  return { error: true, retryable: true, message: `Request timed out (${timeoutMs}ms). Raise --timeout-ms or the provider's timeout_ms.` };
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-11-03
4
- **Commit:** fa007d1f
5
- **Branch:** main
6
- **Version:** 0.35.24.0
3
+ **Generated:** 2026-11-04
4
+ **Commit:** eb2097b0
5
+ **Branch:** sprint/2026-11-03-02
6
+ **Version:** 0.36.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-11-03
4
- **Commit:** fa007d1f
5
- **Branch:** main
6
- **Version:** 0.35.24.0
3
+ **Generated:** 2026-11-04
4
+ **Commit:** eb2097b0
5
+ **Branch:** sprint/2026-11-03-02
6
+ **Version:** 0.36.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-11-03
4
- **Commit:** fa007d1f
5
- **Branch:** main
6
- **Version:** 0.35.24.0
3
+ **Generated:** 2026-11-04
4
+ **Commit:** eb2097b0
5
+ **Branch:** sprint/2026-11-03-02
6
+ **Version:** 0.36.1.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.