@siming-org/cli 0.7.4 → 0.8.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.
package/dist/index.js CHANGED
@@ -675,15 +675,764 @@ import { readFileSync as readFileSync5 } from "fs";
675
675
  import {
676
676
  ARTIFACT_TYPES
677
677
  } from "@siming-org/core";
678
+
679
+ // src/client/checks/types.ts
680
+ var NA_PHRASES = ["\u4E0D\u9002\u7528", "N/A", "not applicable", "n-a"];
681
+ var MIN_AI_CHECK_ITEM_LENGTH = 15;
682
+ var VAGUE_AI_CHECK_BLACKLIST = ["\u5DF2\u5B8C\u6210", "\u901A\u8FC7", "\u5B8C\u6210", "done", "ok", "pass"];
683
+
684
+ // src/client/checks/git-checks.ts
685
+ function naIfRepoUnavailable(ctx) {
686
+ if (ctx.vcsLoadError !== void 0) {
687
+ return { type: "", status: "fail", detail: `\u9879\u76EE vcs \u914D\u7F6E\u8BFB\u53D6\u5931\u8D25\uFF1A${ctx.vcsLoadError}\u2014\u2014\u65E0\u6CD5\u786E\u8BA4\u95E8\u7981\u542F\u7528\u72B6\u6001\uFF08\u62D2\u9759\u9ED8\u964D\u7EA7\uFF09\u3002\u4FEE\u590D\uFF1A\u786E\u8BA4 server \u53EF\u8FBE\u4E0E\u9879\u76EE\u914D\u7F6E\u540E\u91CD\u8DD1 task check` };
688
+ }
689
+ if (ctx.vcs.enabled === false) {
690
+ return na("\u9879\u76EE vcs.enabled=false\uFF08\u58F0\u660E\u65E0\u5206\u652F\u6A21\u578B\uFF09\uFF0Cgit \u7C7B\u68C0\u67E5\u8C41\u514D");
691
+ }
692
+ if (ctx.repoPath === null) {
693
+ return na(`\u4ED3\u5E93\u4E0D\u53EF\u7528\uFF1A${ctx.repoProbeDetail}`);
694
+ }
695
+ return null;
696
+ }
697
+ function na(reason) {
698
+ return { type: "", status: "not_applicable", detail: reason };
699
+ }
700
+ function fail2(ctx, type, rootCause, fix) {
701
+ return { type, status: "fail", detail: `${rootCause}\u3002\u4FEE\u590D\uFF1A${fix}\uFF08\u5F53\u524D nodeId=${ctx.nodeId}\uFF09` };
702
+ }
703
+ async function currentBranch(ctx) {
704
+ const r = await ctx.runGit(["rev-parse", "--abbrev-ref", "HEAD"], ctx.repoPath);
705
+ return r.ok ? r.stdout.trim() : null;
706
+ }
707
+ async function checkBranchExists(ctx) {
708
+ const type = "git-branch-exists";
709
+ const guard = naIfRepoUnavailable(ctx);
710
+ if (guard) return { ...guard, type };
711
+ const branch = await currentBranch(ctx);
712
+ if (branch === null) {
713
+ return fail2(ctx, type, "\u65E0\u6CD5\u8BFB\u53D6\u5F53\u524D\u5206\u652F\uFF08git rev-parse --abbrev-ref HEAD \u5931\u8D25\uFF09", "\u786E\u8BA4\u5728\u4EFB\u52A1\u5206\u652F\u4E0A\u6267\u884C\uFF0C\u6216\u68C0\u67E5\u4ED3\u5E93\u72B6\u6001");
714
+ }
715
+ if (branch === "HEAD") {
716
+ return fail2(ctx, type, "\u5904\u4E8E detached HEAD \u72B6\u6001\uFF08\u672A\u68C0\u51FA\u4EFB\u4F55\u5206\u652F\uFF09", `git checkout \u4EFB\u52A1\u5206\u652F\uFF08\u987B\u5339\u914D ${ctx.branchPattern} \u524D\u7F00\uFF09`);
717
+ }
718
+ if (!branch.startsWith(ctx.branchPattern)) {
719
+ return fail2(ctx, type, `\u5F53\u524D\u5206\u652F\u300C${branch}\u300D\u4E0D\u5339\u914D\u4EFB\u52A1\u5206\u652F\u89C4\u5219\u300C${ctx.branchPattern} \u524D\u7F00\u300D`, `\u5207\u5230\u4EFB\u52A1\u5206\u652F\uFF08feature/Txxxxxxxx...\uFF09\u540E\u518D\u63A8\u8FDB`);
720
+ }
721
+ return { type, status: "pass", detail: `\u5F53\u524D\u5206\u652F ${branch}` };
722
+ }
723
+ async function baseMergedIntoHead(ctx, base) {
724
+ const f = await ctx.runGit(["fetch", "origin", base], ctx.repoPath);
725
+ if (!f.ok) return null;
726
+ const r = await ctx.runGit(["merge-base", "--is-ancestor", `origin/${base}`, "HEAD"], ctx.repoPath);
727
+ return r.ok;
728
+ }
729
+ async function checkBaseMerged(ctx) {
730
+ const type = "git-base-merged";
731
+ const guard = naIfRepoUnavailable(ctx);
732
+ if (guard) return { ...guard, type };
733
+ const waived = findWaivedDecision(ctx, "ALIGN");
734
+ if (waived !== null) {
735
+ return { type, status: "not_applicable", detail: `ALIGN \u8BB0\u5F55\u542B merge-waived \u8C41\u514D\uFF08decision ${waived}\uFF0C\u7528\u6237\u660E\u793A\u4E0D\u9700\u5408\u5E76\uFF09` };
736
+ }
737
+ if (ctx.baseBranch === null) {
738
+ return fail2(ctx, type, "\u57FA\u7840\u5206\u652F\u672A\u77E5\uFF08ALIGN \u8BB0\u5F55\u672A\u5199 baseBranch \u4E14\u9879\u76EE\u672A\u914D\u7F6E vcs.baseBranch\uFF09", "record-set \u5199 ALIGN.baseBranch\uFF0C\u6216 siming project update --vcs-base-branch <\u5206\u652F>");
739
+ }
740
+ const merged = await baseMergedIntoHead(ctx, ctx.baseBranch);
741
+ if (merged === null) {
742
+ return fail2(ctx, type, `git fetch origin ${ctx.baseBranch} \u5931\u8D25\u2014\u2014\u65E0\u6CD5\u4EE5\u8FDC\u7A0B\u6700\u65B0\u5F15\u7528\u5224\u5B9A\uFF08\u62D2\u6309\u9648\u65E7 ref \u8BEF\u5224\uFF09`, "\u68C0\u67E5\u7F51\u7EDC/\u8FDC\u7A0B\u53EF\u8FBE\u540E\u91CD\u8BD5\uFF1B\u786E\u8BA4\u5206\u652F\u5B58\u5728\u4E8E origin");
743
+ }
744
+ if (!merged) {
745
+ return fail2(ctx, type, `origin/${ctx.baseBranch} \u5C1A\u672A\u5408\u5165\u5F53\u524D\u5206\u652F`, `\u6309 ALIGN \u8282\u70B9\u8981\u6C42\u5408\u5E76\u57FA\u7840\u5206\u652F\u8FDC\u7A0B\u6700\u65B0\u4EE3\u7801\uFF08merge ${ctx.baseBranch} \u2192 \u5F53\u524D\u5206\u652F\uFF09\u540E\u91CD\u8BD5`);
746
+ }
747
+ return { type, status: "pass", detail: `origin/${ctx.baseBranch} \u5DF2\u5408\u5165 HEAD` };
748
+ }
749
+ function findWaivedDecision(ctx, nodeId) {
750
+ const record = ctx.nodeRecords[nodeId];
751
+ const hit = record?.decisions.find((d) => d.topic === "merge-waived" && d.decision.trim() !== "");
752
+ return hit ? hit.id : null;
753
+ }
754
+ async function checkWorktreeClean(ctx) {
755
+ const type = "git-worktree-clean";
756
+ const guard = naIfRepoUnavailable(ctx);
757
+ if (guard) return { ...guard, type };
758
+ const r = await ctx.runGit(["status", "--porcelain"], ctx.repoPath);
759
+ if (!r.ok) {
760
+ return fail2(ctx, type, `git status \u5931\u8D25\uFF1A${firstLine(r.stderr)}`, "\u68C0\u67E5\u4ED3\u5E93\u53EF\u7528\u6027");
761
+ }
762
+ if (r.stdout.trim() !== "") {
763
+ const count = r.stdout.trim().split("\n").length;
764
+ const sample = r.stdout.trim().split("\n").slice(0, 3).join("; ");
765
+ return fail2(ctx, type, `\u5DE5\u4F5C\u6811\u4E0D\u5E72\u51C0\uFF08${count} \u5904\u672A\u63D0\u4EA4\u53D8\u66F4\uFF1A${sample}\uFF09`, "\u5B8C\u6210\u672C\u8282\u70B9\u6210\u679C\u63D0\u4EA4\uFF08\u6216\u5199 .gitignore / \u8C41\u514D\u7559\u75D5\uFF09\u540E\u518D\u63A8\u8FDB");
766
+ }
767
+ return { type, status: "pass", detail: "\u5DE5\u4F5C\u6811\u5E72\u51C0" };
768
+ }
769
+ async function checkArchiveMerged(ctx) {
770
+ const type = "git-archive-merged";
771
+ const guard = naIfRepoUnavailable(ctx);
772
+ if (guard) return { ...guard, type };
773
+ if (ctx.vcs.archiveToBase === false) {
774
+ return { type, status: "not_applicable", detail: "\u9879\u76EE vcs.archiveToBase=false\uFF08\u58F0\u660E\u65E0\u5F52\u6863\u5408\u5E76\u6D41\u7A0B\uFF09" };
775
+ }
776
+ const hasSuccessor = (ctx.edges ?? []).some((e) => e.from === ctx.nodeId);
777
+ if (hasSuccessor) {
778
+ return {
779
+ type,
780
+ status: "not_applicable",
781
+ detail: "\u975E\u7EC8\u6001\u5F62\u6001\uFF08\u5B58\u5728\u540E\u7EE7\u8282\u70B9\uFF09\uFF1A\u4EE3\u7801\u5F52\u6863\u7531\u5BA1\u6279\u901A\u8FC7\u540E\u7684\u5F52\u6863\u5F85\u529E\u627F\u8F7D\uFF0Cadvance \u65F6\u5F52\u6863\u5C1A\u672A\u53D1\u751F"
782
+ };
783
+ }
784
+ if (ctx.baseBranch === null) {
785
+ return fail2(ctx, type, "\u57FA\u7840\u5206\u652F\u672A\u77E5\uFF08ACCEPT \u524D\u7F6E\uFF1AALIGN \u5E94\u5DF2\u843D\u76D8 baseBranch\uFF09", "record-set \u5199 ALIGN.baseBranch\uFF0C\u6216 siming project update --vcs-base-branch <\u5206\u652F>");
786
+ }
787
+ const base = ctx.baseBranch;
788
+ const count = await ctx.runGit(["rev-list", "--count", `${base}..HEAD`], ctx.repoPath);
789
+ if (count.ok && count.stdout.trim() === "0") {
790
+ return { type, status: "not_applicable", detail: `\u4EFB\u52A1\u5206\u652F\u76F8\u5BF9 ${base} \u96F6\u63D0\u4EA4\uFF08\u65E0\u4EE3\u7801\u6539\u52A8\uFF0C\u65E0\u9700\u5408\u5E76\uFF09` };
791
+ }
792
+ const fetch2 = await ctx.runGit(["fetch", "origin", base], ctx.repoPath);
793
+ if (!fetch2.ok) {
794
+ return fail2(ctx, type, `git fetch origin ${base} \u5931\u8D25\u2014\u2014\u672C\u5730\u4E0E\u8FDC\u7A0B\u4E00\u81F4\u6027\u65E0\u4ECE\u5224\u5B9A\uFF08\u62D2\u6309\u9648\u65E7 ref \u8BEF\u5224\uFF09`, "\u68C0\u67E5\u7F51\u7EDC/\u8FDC\u7A0B\u53EF\u8FBE\u540E\u91CD\u8BD5");
795
+ }
796
+ const ancestor = await ctx.runGit(["merge-base", "--is-ancestor", "HEAD", base], ctx.repoPath);
797
+ if (!ancestor.ok) {
798
+ return fail2(ctx, type, `\u57FA\u7840\u5206\u652F ${base} \u5C1A\u672A\u5305\u542B\u4EFB\u52A1\u5206\u652F HEAD\uFF08\u672A\u5408\u5E76\uFF09`, `git checkout ${base} && git merge --no-ff <\u4EFB\u52A1\u5206\u652F> && git push`);
799
+ }
800
+ const merges = await ctx.runGit(["log", "--merges", "-1", "--format=%P", base], ctx.repoPath);
801
+ const parents = merges.ok ? merges.stdout.trim().split(/\s+/) : [];
802
+ if (parents.length !== 2) {
803
+ return fail2(ctx, type, `\u672A\u68C0\u6D4B\u5230 --no-ff \u5408\u5E76\u63D0\u4EA4\uFF08\u6700\u8FD1\u4E00\u6B21\u5408\u5E76 parents=${parents.length}\uFF09`, "\u5408\u5E76\u5FC5\u987B\u4F7F\u7528 --no-ff\uFF08\u4FDD\u7559\u5408\u5E76\u8BB0\u5F55\uFF09\uFF1Agit merge --no-ff <\u4EFB\u52A1\u5206\u652F>");
804
+ }
805
+ const localBase = await ctx.runGit(["rev-parse", base], ctx.repoPath);
806
+ const remoteBase = await ctx.runGit(["rev-parse", `origin/${base}`], ctx.repoPath);
807
+ if (!localBase.ok || !remoteBase.ok || localBase.stdout.trim() !== remoteBase.stdout.trim()) {
808
+ return fail2(ctx, type, `\u672C\u5730 ${base} \u4E0E origin/${base} \u4E0D\u4E00\u81F4\uFF08\u5408\u5E76\u7ED3\u679C\u672A\u63A8\u9001\u6216\u672A\u62C9\u53D6\uFF09`, `git push origin ${base}\uFF08\u6216\u5148 pull \u5BF9\u9F50\uFF09`);
809
+ }
810
+ return { type, status: "pass", detail: `\u4EFB\u52A1\u5206\u652F\u5DF2 --no-ff \u5408\u5165 ${base} \u4E14\u5DF2\u4E0E\u8FDC\u7A0B\u4E00\u81F4` };
811
+ }
812
+ async function checkStayOnBase(ctx) {
813
+ const type = "git-stay-on-base";
814
+ const guard = naIfRepoUnavailable(ctx);
815
+ if (guard) return { ...guard, type };
816
+ if (ctx.vcs.worktreeMode === true) {
817
+ return { type, status: "not_applicable", detail: "worktreeMode=true\uFF08worktree \u5E76\u884C\u5F00\u53D1\uFF0C\u4E0D\u8981\u6C42\u505C\u7559\u57FA\u7840\u5206\u652F\uFF09" };
818
+ }
819
+ if (ctx.vcs.archiveToBase === false) {
820
+ return { type, status: "not_applicable", detail: "\u9879\u76EE vcs.archiveToBase=false\uFF08\u65E0\u5F52\u6863\u6D41\u7A0B\uFF0C\u7EC8\u6001\u505C\u7559\u4EFB\u52A1\u5206\u652F\uFF09" };
821
+ }
822
+ if (ctx.baseBranch === null) {
823
+ return fail2(ctx, type, "\u57FA\u7840\u5206\u652F\u672A\u77E5\uFF08ARCHIVE \u524D\u7F6E\uFF1AALIGN \u5E94\u5DF2\u843D\u76D8 baseBranch\uFF09", "record-set \u5199 ALIGN.baseBranch\uFF0C\u6216 siming project update --vcs-base-branch <\u5206\u652F>");
824
+ }
825
+ const branch = await currentBranch(ctx);
826
+ if (branch === null) {
827
+ return fail2(ctx, type, "\u65E0\u6CD5\u8BFB\u53D6\u5F53\u524D\u5206\u652F", "\u786E\u8BA4\u4ED3\u5E93\u72B6\u6001");
828
+ }
829
+ if (branch !== ctx.baseBranch) {
830
+ return fail2(ctx, type, `\u5F52\u6863\u540E\u5E94\u505C\u7559\u57FA\u7840\u5206\u652F\uFF1A\u5F53\u524D\u5728\u300C${branch}\u300D\uFF0C\u671F\u671B\u300C${ctx.baseBranch}\u300D`, `git checkout ${ctx.baseBranch}\uFF08\u7981\u6B62\u7ACB\u5373\u5F00\u59CB\u4E0B\u4E00\u4EFB\u52A1\u5206\u652F\uFF09`);
831
+ }
832
+ return { type, status: "pass", detail: `\u5F53\u524D\u505C\u7559\u5728\u57FA\u7840\u5206\u652F ${branch}` };
833
+ }
834
+ function firstLine(text) {
835
+ return text.trim().split("\n")[0] ?? "";
836
+ }
837
+
838
+ // src/client/checks/record-checks.ts
839
+ function decisionByTopic(record, topic) {
840
+ const hit = record?.decisions.find((d) => d.topic === topic && d.decision.trim() !== "");
841
+ return hit ?? null;
842
+ }
843
+ async function checkTestReport(ctx) {
844
+ const type = "record-test-report";
845
+ const report = ctx.record?.testReport;
846
+ if (!report) {
847
+ return fail3(type, "\u672C\u8282\u70B9\u8BB0\u5F55\u672A\u5199 testReport\uFF08\u7ED3\u6784\u5316\u5B57\u6BB5\u7F3A\u5931\uFF09", "record-set \u5199 testReport {command,passed,failed,skipped}\uFF08\u771F\u5B9E\u6267\u884C\u7684\u6D4B\u8BD5\u547D\u4EE4\u4E0E\u7ED3\u679C\u8BA1\u6570\uFF09");
848
+ }
849
+ if (report.failed > 0) {
850
+ return fail3(type, `\u6D4B\u8BD5\u5B58\u5728\u5931\u8D25\uFF1Afailed=${report.failed}\uFF08${report.command}\uFF09`, "\u4FEE\u590D\u5931\u8D25\u7528\u4F8B\u6216\u7ECF\u7528\u6237\u6388\u6743\u540E\u8D70\u8C41\u514D\u901A\u9053\uFF0C\u7981\u6B62\u5E26\u5931\u8D25\u63A8\u8FDB");
851
+ }
852
+ if (report.skipped > 0) {
853
+ const waiver = decisionByTopic(ctx.record, "skip-authorized");
854
+ if (waiver === null) {
855
+ return fail3(type, `\u8DF3\u8FC7\u7528\u4F8B ${report.skipped} \u4E2A\u4F46\u65E0\u7528\u6237\u6388\u6743\u7559\u75D5\uFF08${report.command}\uFF09`, "\u7ECF\u7528\u6237\u660E\u793A\u6388\u6743\u540E record-decision-add \u5199 topic=skip-authorized\uFF0C\u6216\u6E05\u96F6 skipped");
856
+ }
857
+ return { type, status: "pass", detail: `\u6D4B\u8BD5\u901A\u8FC7\uFF08skipped=${report.skipped}\uFF0Cskip-authorized \u8C41\u514D decision ${waiver.id}\uFF09` };
858
+ }
859
+ return { type, status: "pass", detail: `\u6D4B\u8BD5\u5168\u7EFF\uFF1Apassed=${report.passed}\uFF08${report.command}\uFF09` };
860
+ }
861
+ async function checkNoFakeGreen(ctx) {
862
+ const type = "record-no-fake-green";
863
+ const waiver = decisionByTopic(ctx.record, "test-adjust-approved");
864
+ if (waiver !== null) {
865
+ return { type, status: "pass", detail: `\u6D4B\u8BD5\u8C03\u6574\u6301\u6279\u51C6\u7559\u75D5\uFF08decision ${waiver.id}\uFF09` };
866
+ }
867
+ const guard = naIfRepoUnavailable(ctx);
868
+ if (guard) return { ...guard, type };
869
+ if (ctx.baseBranch === null) {
870
+ return fail3(type, "\u57FA\u7840\u5206\u652F\u672A\u77E5\uFF08ALIGN.baseBranch \u672A\u843D\u76D8\u4E14\u9879\u76EE\u672A\u914D\u7F6E\uFF09", "record-set \u5199 ALIGN.baseBranch\uFF0C\u6216 siming project update --vcs-base-branch <\u5206\u652F>");
871
+ }
872
+ const diff = await ctx.runGit(
873
+ ["diff", `${ctx.baseBranch}...HEAD`, "--", "**/*.test.ts", "**/*.spec.ts", "**/tests/**"],
874
+ ctx.repoPath
875
+ );
876
+ if (!diff.ok) {
877
+ return fail3(type, `\u6D4B\u8BD5\u6587\u4EF6 diff \u5931\u8D25\uFF1A${firstLine2(diff.stderr)}`, "\u68C0\u67E5\u4ED3\u5E93\u4E0E\u57FA\u7840\u5206\u652F\u53EF\u7528\u6027");
878
+ }
879
+ const violations = scanTestDiff(diff.stdout);
880
+ if (violations.length > 0) {
881
+ return fail3(type, `\u6D4B\u8BD5\u5B8C\u6574\u6027\u8FDD\u89C4\uFF1A${violations.join("\uFF1B")}`, "\u8FD8\u539F\u6D4B\u8BD5\u6539\u52A8\uFF1B\u786E\u5C5E\u5408\u6CD5\u8C03\u6574\u987B\u7ECF\u7528\u6237\u6279\u51C6\u540E record-decision-add \u5199 topic=test-adjust-approved");
882
+ }
883
+ return { type, status: "pass", detail: "\u6D4B\u8BD5\u6587\u4EF6\u65E0 skip/only \u65B0\u589E\u4E0E\u6574\u5220\uFF08\u653E\u5BBD\u65AD\u8A00\u5F62\u6001\u4E0D\u5728\u673A\u5668\u68C0\u6D4B\u9762\uFF0C\u4F9D\u8D56 test-adjust-approved \u7559\u75D5\u628A\u5173\uFF09" };
884
+ }
885
+ function scanTestDiff(patch) {
886
+ const violations = [];
887
+ let currentFile = "";
888
+ let deleting = false;
889
+ for (const line of patch.split("\n")) {
890
+ if (line.startsWith("diff --git ")) {
891
+ currentFile = line;
892
+ deleting = false;
893
+ continue;
894
+ }
895
+ if (line.startsWith("deleted file mode")) {
896
+ const m = currentFile.match(/^diff --git a\/(.+) b\//);
897
+ if (m) violations.push({ reason: `\u6D4B\u8BD5\u6587\u4EF6\u6574\u5220\uFF1A${m[1]}` });
898
+ deleting = true;
899
+ continue;
900
+ }
901
+ if (deleting) continue;
902
+ if (line.startsWith("+") && !line.startsWith("+++")) {
903
+ if (line.includes(".skip(") || line.includes(".only(")) {
904
+ violations.push({ reason: `\u65B0\u589E\u8DF3\u8FC7/\u805A\u7126\u6807\u8BB0\uFF1A${line.slice(1).trim().slice(0, 80)}` });
905
+ }
906
+ }
907
+ }
908
+ return violations.map((v) => v.reason);
909
+ }
910
+ async function checkBaseReMerged(ctx) {
911
+ const type = "record-base-re-merged";
912
+ const guard = naIfRepoUnavailable(ctx);
913
+ if (guard) return { ...guard, type };
914
+ const waived = findWaivedDecision(ctx, "INTEGRATE");
915
+ if (waived !== null) {
916
+ return { type, status: "not_applicable", detail: `INTEGRATE \u8BB0\u5F55\u542B merge-waived \u8C41\u514D\uFF08decision ${waived}\uFF09` };
917
+ }
918
+ if (ctx.baseBranch === null) {
919
+ return fail3(type, "\u57FA\u7840\u5206\u652F\u672A\u77E5\uFF08ALIGN.baseBranch \u672A\u843D\u76D8\u4E14\u9879\u76EE\u672A\u914D\u7F6E\uFF09", "record-set \u5199 ALIGN.baseBranch\uFF0C\u6216 siming project update --vcs-base-branch <\u5206\u652F>");
920
+ }
921
+ const fetch2 = await ctx.runGit(["fetch", "origin", ctx.baseBranch], ctx.repoPath);
922
+ if (!fetch2.ok) {
923
+ return fail3(type, `git fetch origin ${ctx.baseBranch} \u5931\u8D25\u2014\u2014\u65E0\u6CD5\u4EE5\u8FDC\u7A0B\u6700\u65B0\u5F15\u7528\u5224\u5B9A\uFF08\u62D2\u6309\u9648\u65E7 ref \u8BEF\u5224\uFF09`, "\u68C0\u67E5\u7F51\u7EDC/\u8FDC\u7A0B\u53EF\u8FBE\u540E\u91CD\u8BD5");
924
+ }
925
+ const r = await ctx.runGit(["merge-base", "--is-ancestor", `origin/${ctx.baseBranch}`, "HEAD"], ctx.repoPath);
926
+ if (!r.ok) {
927
+ return fail3(type, `origin/${ctx.baseBranch} \u589E\u91CF\u672A\u518D\u5408\u5165\u5F53\u524D\u5206\u652F\uFF08\u96C6\u6210\u524D\u57FA\u7840\u5206\u652F\u6709\u65B0\u63D0\u4EA4\uFF09`, `merge ${ctx.baseBranch} \u2192 \u5F53\u524D\u5206\u652F\uFF08\u6D88\u89E3\u6F02\u79FB\uFF09\u540E\u518D\u63A8\u8FDB`);
928
+ }
929
+ return { type, status: "pass", detail: `origin/${ctx.baseBranch} \u5DF2\u518D\u5408\u5165 HEAD` };
930
+ }
931
+ async function checkFixReviewPass(ctx) {
932
+ const type = "record-fix-review-pass";
933
+ const fixes = ctx.record?.fixesApplied ?? 0;
934
+ if (fixes > 0) {
935
+ const verdict = ctx.record?.review?.verdict;
936
+ if (verdict !== "pass") {
937
+ return fail3(type, `\u58F0\u660E\u4FEE\u590D ${fixes} \u5904\u4F46\u8BC4\u5BA1\u7ED3\u8BBA\u975E pass\uFF08verdict=${verdict ?? "\u672A\u5199"}\uFF09`, "\u4FEE\u590D\u540E\u91CD\u4EA4 code-reviewer \u8BC4\u5BA1\u81F3 PASS\uFF08record-review-set\uFF09\uFF0C\u6216\u5C06 fixesApplied \u5982\u5B9E\u5F52\u96F6");
938
+ }
939
+ return { type, status: "pass", detail: `\u4FEE\u590D ${fixes} \u5904\u4E14\u8BC4\u5BA1 PASS` };
940
+ }
941
+ return { type, status: "pass", detail: "\u65E0\u4FEE\u590D\uFF08fixesApplied=0\uFF09" };
942
+ }
943
+ async function checkVerifiedCommit(ctx) {
944
+ const type = "record-verified-commit";
945
+ const sha = ctx.record?.verifiedCommit;
946
+ if (!sha) {
947
+ return fail3(type, "\u672C\u8282\u70B9\u8BB0\u5F55\u672A\u5199 verifiedCommit\uFF08\u96C6\u6210\u9A8C\u8BC1\u7248\u672C\u7F3A\u5931\uFF09", "record-set \u5199 verifiedCommit <sha>\uFF08\u7ECF\u9A8C\u8BC1\u7684 HEAD \u63D0\u4EA4\u53F7\uFF09");
948
+ }
949
+ const guard = naIfRepoUnavailable(ctx);
950
+ if (guard) return { ...guard, type };
951
+ const r = await ctx.runGit(["cat-file", "-e", sha], ctx.repoPath);
952
+ if (!r.ok) {
953
+ return fail3(type, `verifiedCommit\u300C${sha}\u300D\u5728\u4ED3\u5E93\u4E2D\u4E0D\u5B58\u5728\uFF08cat-file \u5931\u8D25\uFF09`, "record-set \u4FEE\u6B63 verifiedCommit \u4E3A\u771F\u5B9E\u5B58\u5728\u7684\u63D0\u4EA4\u53F7");
954
+ }
955
+ return { type, status: "pass", detail: `\u9A8C\u8BC1\u7248\u672C ${sha} \u5B58\u5728` };
956
+ }
957
+ async function checkPrevNodesDone(ctx) {
958
+ const type = "record-prev-nodes-done";
959
+ const predecessors = collectPredecessors(ctx.edges ?? [], ctx.nodeId);
960
+ const pending = ctx.nodes.filter(
961
+ (n) => predecessors.has(n.nodeId) && n.status !== "skipped" && n.status !== "completed"
962
+ );
963
+ if (pending.length > 0) {
964
+ const summary = pending.map((n) => `${n.nodeId}:${n.status}`).join(", ");
965
+ return fail3(type, `\u5B58\u5728\u672A\u5B8C\u6210\u524D\u5E8F\u8282\u70B9\uFF08${summary}\uFF09`, "\u6309 DAG \u987A\u5E8F\u5B8C\u6210\u524D\u7F6E\u8282\u70B9\uFF08\u6216\u660E\u786E\u8DF3\u8FC7\u8DEF\u5F84\uFF09\u540E\u518D\u63A8\u8FDB\u9A8C\u6536");
966
+ }
967
+ if (predecessors.size === 0) {
968
+ return { type, status: "pass", detail: "\u65E0\u6709\u5411\u8FB9\u53EF\u5224\u5B9A\u524D\u5E8F\uFF08\u95ED\u5305\u4E3A\u7A7A\uFF09\u2014\u2014\u89C6\u4E3A\u901A\u8FC7" };
969
+ }
970
+ return { type, status: "pass", detail: "\u524D\u5E8F\u8282\u70B9\u5168\u90E8\u5B8C\u6210\u6216\u5DF2\u8DF3\u8FC7" };
971
+ }
972
+ function collectPredecessors(edges, nodeId) {
973
+ const incoming = /* @__PURE__ */ new Map();
974
+ for (const e of edges) {
975
+ const list = incoming.get(e.to) ?? [];
976
+ list.push(e.from);
977
+ incoming.set(e.to, list);
978
+ }
979
+ const seen = /* @__PURE__ */ new Set();
980
+ const queue = [...incoming.get(nodeId) ?? []];
981
+ while (queue.length > 0) {
982
+ const cur = queue.pop();
983
+ if (seen.has(cur)) continue;
984
+ seen.add(cur);
985
+ for (const prev of incoming.get(cur) ?? []) queue.push(prev);
986
+ }
987
+ return seen;
988
+ }
989
+ async function checkVersionConsistent(ctx) {
990
+ const type = "record-version-consistent";
991
+ const sha = ctx.nodeRecords["INTEGRATE"]?.verifiedCommit;
992
+ if (!sha) {
993
+ return fail3(type, "INTEGRATE \u8BB0\u5F55\u7F3A verifiedCommit\uFF08\u7248\u672C\u4E00\u81F4\u6027\u65E0\u4ECE\u5224\u5B9A\uFF09", "\u56DE\u8865 INTEGRATE record-set verifiedCommit \u540E\u91CD\u8BD5");
994
+ }
995
+ const guard = naIfRepoUnavailable(ctx);
996
+ if (guard) return { ...guard, type };
997
+ if (ctx.baseBranch === null) {
998
+ return fail3(type, "\u57FA\u7840\u5206\u652F\u672A\u77E5\uFF08ALIGN.baseBranch \u672A\u843D\u76D8\u4E14\u9879\u76EE\u672A\u914D\u7F6E\uFF09", "record-set \u5199 ALIGN.baseBranch\uFF0C\u6216 siming project update --vcs-base-branch <\u5206\u652F>");
999
+ }
1000
+ const r = await ctx.runGit(["diff", "--name-only", `${sha}..${ctx.baseBranch}`], ctx.repoPath);
1001
+ if (!r.ok) {
1002
+ return fail3(type, `\u7248\u672C diff \u5931\u8D25\uFF1A${firstLine2(r.stderr)}`, "\u68C0\u67E5 verifiedCommit \u4E0E\u57FA\u7840\u5206\u652F\u53EF\u7528\u6027");
1003
+ }
1004
+ const exempt = (path) => ctx.docPaths.some((p) => {
1005
+ if (p.endsWith("/")) return path.startsWith(p);
1006
+ if (p.startsWith("*")) return path.endsWith(p.slice(1));
1007
+ return path === p;
1008
+ });
1009
+ const leaked = r.stdout.split("\n").map((l) => l.trim()).filter((l) => l !== "" && !exempt(l));
1010
+ if (leaked.length > 0) {
1011
+ return fail3(
1012
+ type,
1013
+ `verifiedCommit \u4E0E ${ctx.baseBranch} \u5B58\u5728\u975E\u6587\u6863\u5DEE\u5F02\uFF08${leaked.slice(0, 5).join(", ")}${leaked.length > 5 ? ` \u7B49 ${leaked.length} \u4E2A` : ""}\uFF09`,
1014
+ "\u4EE3\u7801\u6539\u52A8\u987B\u5408\u5E76\u8FDB\u57FA\u7840\u5206\u652F\uFF08\u542B\u5E76\u884C\u4EFB\u52A1 base \u524D\u8FDB\u573A\u666F\uFF1A\u91CD\u8D70\u5408\u5E76\u6216\u7ECF\u7528\u6237\u660E\u793A\u540E force \u63A8\u8FDB\u5E76\u7559\u75D5\uFF09"
1015
+ );
1016
+ }
1017
+ return { type, status: "pass", detail: `verifiedCommit ${sha} \u4E0E ${ctx.baseBranch} \u4E00\u81F4\uFF08\u6587\u6863\u8DEF\u5F84\u8C41\u514D ${ctx.docPaths.join(" ")}\uFF09` };
1018
+ }
1019
+ async function checkDeferredIssues(ctx) {
1020
+ const type = "record-deferred-issues";
1021
+ const ids = ctx.record?.deferredIssues ?? [];
1022
+ if (ids.length === 0) {
1023
+ return { type, status: "pass", detail: "\u65E0\u5EF6\u671F\u573A\u666F" };
1024
+ }
1025
+ const loaded = await ctx.loadIssueIds();
1026
+ if (!loaded.ok) {
1027
+ return fail3(type, `\u5173\u8054\u95EE\u9898\u53CD\u67E5\u5931\u8D25\uFF1A${loaded.error}`, "\u68C0\u67E5 server \u53EF\u7528\u6027\u540E\u91CD\u8BD5");
1028
+ }
1029
+ const missing = ids.filter((id) => !loaded.ids.has(id));
1030
+ if (missing.length > 0) {
1031
+ return fail3(type, `\u5EF6\u671F\u95EE\u9898\u672A\u843D\u6C60\uFF1A${missing.join(", ")}`, "siming issue create \u5C06\u5EF6\u671F\u573A\u666F\u5F55\u5165\u95EE\u9898\u6C60\u540E\uFF0Crecord-set \u4FEE\u6B63 deferredIssues");
1032
+ }
1033
+ return { type, status: "pass", detail: `\u5EF6\u671F\u95EE\u9898 ${ids.length} \u4E2A\u5747\u5DF2\u843D\u6C60` };
1034
+ }
1035
+ async function checkEvidenceFiles(ctx) {
1036
+ const type = "record-evidence-files";
1037
+ const files = ctx.record?.evidenceFiles ?? [];
1038
+ if (files.length === 0) {
1039
+ return fail3(type, "\u672C\u8282\u70B9\u8BB0\u5F55\u672A\u5199 evidenceFiles\uFF08\u89C6\u89C9\u9A8C\u6536\u8BC1\u636E\u7F3A\u5931\uFF09", "record-set \u5199 evidenceFiles\uFF08\u622A\u56FE/\u5F55\u5C4F\u76F8\u5BF9\u8DEF\u5F84\u6E05\u5355\uFF09");
1040
+ }
1041
+ if (ctx.repoPath === null) {
1042
+ return { type, status: "not_applicable", detail: `\u4ED3\u5E93\u4E0D\u53EF\u7528\uFF1A${ctx.repoProbeDetail}` };
1043
+ }
1044
+ const missing = [];
1045
+ for (const rel of files) {
1046
+ if (!ctx.fileExists(`${ctx.repoPath}/${rel}`)) missing.push(rel);
1047
+ }
1048
+ if (missing.length > 0) {
1049
+ return fail3(type, `\u8BC1\u636E\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${missing.join(", ")}`, "\u8865\u9F50\u8BC1\u636E\u6587\u4EF6\u6216 record-set \u4FEE\u6B63 evidenceFiles \u8DEF\u5F84");
1050
+ }
1051
+ return { type, status: "pass", detail: `\u8BC1\u636E\u6587\u4EF6 ${files.length} \u4E2A\u5747\u5B58\u5728` };
1052
+ }
1053
+ async function checkFinalConverged(ctx) {
1054
+ const type = "record-final-converged";
1055
+ const record = ctx.nodeRecords["ARCHIVE"] ?? ctx.record;
1056
+ const confirmations = record?.confirmations ?? [];
1057
+ const summary = record?.summary ?? "";
1058
+ if (confirmations.length === 0 || summary.trim() === "") {
1059
+ const missing = [
1060
+ ...confirmations.length === 0 ? ["confirmations\uFF08\u7528\u6237\u786E\u8BA4\u7559\u75D5\uFF09"] : [],
1061
+ ...summary.trim() === "" ? ["summary\uFF08\u5F52\u6863\u6458\u8981\uFF09"] : []
1062
+ ];
1063
+ return fail3(type, `\u5F52\u6863\u8BB0\u5F55\u4E0D\u5B8C\u6574\uFF1A\u7F3A ${missing.join(" \u4E0E ")}`, "record-summary-set \u5199\u6458\u8981 + record-confirm-add \u5199\u7528\u6237\u786E\u8BA4\u539F\u6587\u540E\u518D\u63A8\u8FDB");
1064
+ }
1065
+ return { type, status: "pass", detail: "\u5F52\u6863\u6458\u8981\u4E0E\u7528\u6237\u786E\u8BA4\u9F50\u5907" };
1066
+ }
1067
+ function fail3(type, rootCause, fix) {
1068
+ return { type, status: "fail", detail: `${rootCause}\u3002\u4FEE\u590D\uFF1A${fix}` };
1069
+ }
1070
+ function firstLine2(text) {
1071
+ return text.trim().split("\n")[0] ?? "";
1072
+ }
1073
+
1074
+ // src/client/checks/registry.ts
1075
+ var CHECK_REGISTRY = {
1076
+ "git-branch-exists": { run: checkBranchExists },
1077
+ "git-base-merged": { run: checkBaseMerged },
1078
+ "git-base-recorded": { run: checkBaseRecorded },
1079
+ "git-worktree-clean": { run: checkWorktreeClean },
1080
+ "git-archive-merged": { run: checkArchiveMerged },
1081
+ "git-stay-on-base": { run: checkStayOnBase },
1082
+ "record-test-report": { run: checkTestReport },
1083
+ "record-no-fake-green": { run: checkNoFakeGreen },
1084
+ "record-base-re-merged": { run: checkBaseReMerged },
1085
+ "record-fix-review-pass": { run: checkFixReviewPass },
1086
+ "record-verified-commit": { run: checkVerifiedCommit },
1087
+ "record-prev-nodes-done": { run: checkPrevNodesDone },
1088
+ "record-version-consistent": { run: checkVersionConsistent },
1089
+ "record-deferred-issues": { run: checkDeferredIssues },
1090
+ "record-evidence-files": { run: checkEvidenceFiles },
1091
+ "record-final-converged": { run: checkFinalConverged }
1092
+ };
1093
+ async function checkBaseRecorded(ctx) {
1094
+ const type = "git-base-recorded";
1095
+ const alignRecord = ctx.nodeRecords["ALIGN"];
1096
+ if (alignRecord?.baseBranch !== void 0 && alignRecord.baseBranch !== "") {
1097
+ return { type, status: "pass", detail: `\u57FA\u7840\u5206\u652F\u5DF2\u843D\u76D8\uFF1A${alignRecord.baseBranch}` };
1098
+ }
1099
+ return {
1100
+ type,
1101
+ status: "fail",
1102
+ detail: "ALIGN \u8BB0\u5F55\u672A\u5199 baseBranch\uFF08\u540E\u7EED\u5408\u5E76/\u5F52\u6863\u7C7B\u68C0\u67E5\u7684\u57FA\u7840\uFF09\u3002\u4FEE\u590D\uFF1Asiming task record set --node ALIGN --base-branch <\u5206\u652F\u540D>"
1103
+ };
1104
+ }
1105
+ function auditAiChecks(aiChecks, record) {
1106
+ const results = [];
1107
+ for (const check of aiChecks) {
1108
+ const confirmed = (record?.checks ?? []).some((c) => c.refId === check.id && c.passed === true);
1109
+ if (confirmed) {
1110
+ results.push({ id: check.id, item: check.item, status: "pass", detail: "\u5B8C\u6210\u5224\u5B9A\u9879\u5DF2\u81EA\u68C0\u901A\u8FC7\uFF08refId \u5173\u8054\uFF09" });
1111
+ continue;
1112
+ }
1113
+ const rejected = (record?.checks ?? []).some((c) => c.refId === check.id && c.passed === false);
1114
+ if (rejected) {
1115
+ results.push({
1116
+ id: check.id,
1117
+ item: check.item,
1118
+ status: "fail",
1119
+ detail: `\u7559\u75D5\u7ED3\u8BBA\u672A\u901A\u8FC7\uFF08check ${check.id} passed=false\uFF09\u3002\u4FEE\u590D\uFF1A\u91CD\u505A\u540E\u7FFB\u7EFF\uFF08record-check-patch --check ${check.id} --passed\uFF09\u6216\u5982\u5B9E\u8865\u5199\u4E0D\u9002\u7528`
1120
+ });
1121
+ continue;
1122
+ }
1123
+ const lowered = check.item.toLowerCase();
1124
+ if (NA_PHRASES.some((p) => lowered.includes(p.toLowerCase()))) {
1125
+ results.push({ id: check.id, item: check.item, status: "not_applicable", detail: "item \u58F0\u660E\u4E0D\u9002\u7528\uFF08N/A \u8BCD\u8868\u547D\u4E2D\uFF09" });
1126
+ continue;
1127
+ }
1128
+ if (check.item.length < MIN_AI_CHECK_ITEM_LENGTH) {
1129
+ results.push({
1130
+ id: check.id,
1131
+ item: check.item,
1132
+ status: "fail",
1133
+ detail: `item \u8FC7\u77ED\uFF08${check.item.length} < ${MIN_AI_CHECK_ITEM_LENGTH} \u5B57\u7B26\uFF0C\u6700\u5C0F\u5F62\u6001\u8981\u6C42\u53EF\u6838\u5BF9\u7684\u5177\u4F53\u4E8B\u5B9E\uFF09`
1134
+ });
1135
+ continue;
1136
+ }
1137
+ if (VAGUE_AI_CHECK_BLACKLIST.some((w) => lowered.includes(w))) {
1138
+ results.push({ id: check.id, item: check.item, status: "fail", detail: `item \u4E3A\u6A21\u7CCA\u5B8C\u6210\u8BED\uFF08\u547D\u4E2D\u9ED1\u540D\u5355\u300C${VAGUE_AI_CHECK_BLACKLIST.join("/")}\u300D\uFF09\uFF0C\u987B\u6539\u5199\u4E3A\u53EF\u6838\u5BF9\u7684\u5177\u4F53\u4E8B\u5B9E` });
1139
+ continue;
1140
+ }
1141
+ results.push({
1142
+ id: check.id,
1143
+ item: check.item,
1144
+ status: "fail",
1145
+ // T202609230001:source 枚举仅 'self'|'tool'(本版工具不写 source,D4)——审计只认 refId 关联
1146
+ detail: "\u5B8C\u6210\u5224\u5B9A\u9879\u672A\u81EA\u68C0\uFF08checks \u65E0 refId \u5173\u8054\u4E14\u672A\u58F0\u660E\u4E0D\u9002\u7528\uFF09\u3002\u4FEE\u590D\uFF1Arecord-check-add \u5E26 refId=" + check.id + " passed=true \u9010\u9879\u81EA\u68C0"
1147
+ });
1148
+ }
1149
+ return results;
1150
+ }
1151
+
1152
+ // src/client/checks/param-resolver.ts
1153
+ import { DEFAULT_VCS_DOC_PATHS } from "@siming-org/core";
1154
+ import { execFile } from "child_process";
1155
+ import { promisify } from "util";
1156
+ var execFileAsync = promisify(execFile);
1157
+ var defaultGitRunner = async (args, cwd) => {
1158
+ try {
1159
+ const { stdout, stderr } = await execFileAsync("git", args, { cwd, timeout: 1e4 });
1160
+ return { ok: true, stdout, stderr };
1161
+ } catch (err2) {
1162
+ const e = err2;
1163
+ return { ok: false, stdout: e.stdout ?? "", stderr: e.stderr ?? e.message ?? String(err2) };
1164
+ }
1165
+ };
1166
+ async function resolveCheckContextWithRunner(base, vcs, cwd, runGit) {
1167
+ const probe = vcs.repoPath !== void 0 ? await probeRepo(vcs.repoPath, runGit) : await probeRepo(cwd, runGit);
1168
+ const alignRecord = base.nodeRecords["ALIGN"];
1169
+ const baseBranch = (alignRecord?.baseBranch !== void 0 && alignRecord.baseBranch !== "" ? alignRecord.baseBranch : void 0) ?? vcs.baseBranch ?? null;
1170
+ return {
1171
+ ...base,
1172
+ repoPath: probe.repoPath,
1173
+ repoProbeDetail: probe.detail,
1174
+ baseBranch,
1175
+ branchPattern: vcs.branchPattern ?? "feature/",
1176
+ docPaths: vcs.docPaths ?? [...DEFAULT_VCS_DOC_PATHS]
1177
+ };
1178
+ }
1179
+ async function probeRepo(candidate, runGit) {
1180
+ const r = await runGit(["rev-parse", "--show-toplevel"], candidate);
1181
+ if (!r.ok) {
1182
+ return {
1183
+ repoPath: null,
1184
+ detail: `\u4ED3\u5E93\u63A2\u6D4B\u5931\u8D25\uFF1ArepoPath \u5019\u9009\u300C${candidate}\u300D\uFF08git rev-parse --show-toplevel \u5931\u8D25\uFF1A${firstLine3(r.stderr)}\uFF09`
1185
+ };
1186
+ }
1187
+ return { repoPath: firstLine3(r.stdout), detail: "" };
1188
+ }
1189
+ function firstLine3(text) {
1190
+ return text.trim().split("\n")[0] ?? "";
1191
+ }
1192
+
1193
+ // src/client/checks/runner.ts
1194
+ import { existsSync as existsSync5 } from "fs";
1195
+ function gateHasFailure(report) {
1196
+ return report.toolOutcomes.some((o) => o.status === "fail") || report.aiOutcomes.some((o) => o.status === "fail");
1197
+ }
1198
+ async function runNodeGate(client, taskId) {
1199
+ const ctxRes = await client.getJson(`/api/tasks/${encodeURIComponent(taskId)}/context?view=basic`);
1200
+ if (!ctxRes.success) return ctxRes;
1201
+ const context = ctxRes.data;
1202
+ const node = context.currentNode;
1203
+ if (node === null) {
1204
+ return ok2({ empty: true, nodeId: context.task.currentNode, toolOutcomes: [], aiOutcomes: [] });
1205
+ }
1206
+ const toolChecks = node.toolChecks ?? [];
1207
+ const aiChecks = node.aiChecks ?? [];
1208
+ if (toolChecks.length === 0 && aiChecks.length === 0) {
1209
+ return ok2({ empty: true, nodeId: node.nodeId, toolOutcomes: [], aiOutcomes: [] });
1210
+ }
1211
+ let vcs = {};
1212
+ let vcsLoadError;
1213
+ const projectRes = await client.getJson(
1214
+ `/api/projects/${encodeURIComponent(context.task.projectId)}`
1215
+ );
1216
+ if (projectRes.success && projectRes.data.vcs !== void 0) {
1217
+ vcs = projectRes.data.vcs;
1218
+ } else if (!projectRes.success) {
1219
+ vcsLoadError = projectRes.error.message;
1220
+ }
1221
+ const nodes = context.nodes.map((n) => ({
1222
+ nodeId: n.nodeId,
1223
+ label: n.label,
1224
+ status: n.status
1225
+ }));
1226
+ const checkCtx = await resolveCheckContextWithRunner(
1227
+ {
1228
+ taskId,
1229
+ nodeId: node.nodeId,
1230
+ record: context.nodeRecords[node.nodeId],
1231
+ nodeRecords: context.nodeRecords,
1232
+ nodes,
1233
+ edges: context.edges ?? [],
1234
+ vcs,
1235
+ ...vcsLoadError !== void 0 ? { vcsLoadError } : {},
1236
+ runGit: defaultGitRunner,
1237
+ loadIssueIds: async () => {
1238
+ const r = await client.getJson(
1239
+ `/api/tasks/${encodeURIComponent(taskId)}/issues`
1240
+ );
1241
+ if (!r.success) return { ok: false, error: r.error.message };
1242
+ const ids = new Set(r.data.items.map((i) => i.issueId ?? i.id ?? ""));
1243
+ ids.delete("");
1244
+ return { ok: true, ids };
1245
+ },
1246
+ fileExists: (p) => existsSync5(p)
1247
+ },
1248
+ vcs,
1249
+ process.cwd(),
1250
+ defaultGitRunner
1251
+ );
1252
+ const toolOutcomes = [];
1253
+ for (const toolCheck of toolChecks) {
1254
+ const def = CHECK_REGISTRY[toolCheck.type];
1255
+ if (def === void 0) {
1256
+ toolOutcomes.push({
1257
+ type: toolCheck.type,
1258
+ status: "fail",
1259
+ detail: "\u672A\u77E5\u68C0\u67E5\u7C7B\u578B\uFF08\u5F53\u524D CLI \u7248\u672C\u4E0D\u542B\u8BE5\u68C0\u67E5\u5B9E\u73B0\uFF09\u3002\u4FEE\u590D\uFF1A\u5347\u7EA7 siming CLI \u540E\u91CD\u8BD5"
1260
+ });
1261
+ continue;
1262
+ }
1263
+ toolOutcomes.push(await def.run(checkCtx));
1264
+ }
1265
+ const aiOutcomes = auditAiChecks(aiChecks, context.nodeRecords[node.nodeId]);
1266
+ return ok2({ empty: false, nodeId: node.nodeId, toolOutcomes, aiOutcomes });
1267
+ }
1268
+ function buildCheckReportPayload(report, mode, note) {
1269
+ const results = [
1270
+ ...report.toolOutcomes.map((o) => ({ type: o.type, status: o.status, detail: o.detail })),
1271
+ ...report.aiOutcomes.map((o) => ({ type: `ai:${o.id}`, status: o.status, detail: o.detail }))
1272
+ ];
1273
+ return { nodeId: report.nodeId, mode, results, ...note !== void 0 ? { note } : {} };
1274
+ }
1275
+ function buildGateFailMessage(report, reportWritten = true) {
1276
+ const fails = [
1277
+ ...report.toolOutcomes.filter((o) => o.status === "fail").map((o) => `[${o.type}] ${o.detail}`),
1278
+ ...report.aiOutcomes.filter((o) => o.status === "fail").map((o) => `[ai:${o.id}] ${o.detail}`)
1279
+ ];
1280
+ const passed = report.toolOutcomes.filter((o) => o.status !== "fail").length + report.aiOutcomes.filter((o) => o.status !== "fail").length;
1281
+ return {
1282
+ message: `\u8282\u70B9\u95E8\u7981\u672A\u901A\u8FC7\uFF08${fails.length} \u9879 fail\uFF0C${passed} \u9879\u901A\u8FC7/\u4E0D\u9002\u7528\uFF09\uFF0Cadvance \u5DF2\u62E6\u622A\uFF1A
1283
+ ${fails.map((f, i) => `${i + 1}. ${f}`).join("\n")}`,
1284
+ hint: reportWritten ? "\u9010\u9879\u5B8C\u6210\u4FEE\u590D\u6216\u7528\u6237\u660E\u793A\u8C41\u514D\uFF08\u7ECF record-decision-add \u7559\u75D5\uFF09\u540E\u91CD\u65B0\u63A8\u8FDB\uFF1B\u95E8\u7981\u7ED3\u679C\u5DF2\u5199\u5165\u4EFB\u52A1 history\uFF08checks_rejected\uFF09" : "\u9010\u9879\u5B8C\u6210\u4FEE\u590D\u6216\u7528\u6237\u660E\u793A\u8C41\u514D\uFF08\u7ECF record-decision-add \u7559\u75D5\uFF09\u540E\u91CD\u65B0\u63A8\u8FDB\u3002\u6CE8\u610F\uFF1A\u672C\u6B21\u95E8\u7981\u7ED3\u679C\u7559\u75D5\u5931\u8D25\uFF0Chistory \u7F3A\u5931\u8BE5\u6B21\u8BB0\u5F55\uFF08\u4FEE\u590D\u540E\u91CD\u63A8\u65F6\u4F1A\u91CD\u65B0\u7559\u75D5\uFF09"
1285
+ };
1286
+ }
1287
+ function naCategory(detail) {
1288
+ return detail.split(/[((::,,;;]/)[0]?.trim().slice(0, 16) ?? "";
1289
+ }
1290
+ function buildPassNote(report) {
1291
+ const mark = (status, key, detail) => {
1292
+ if (status === "pass") return `${key}\u2713`;
1293
+ if (status === "not_applicable") return `${key}=N(${naCategory(detail)})`;
1294
+ return `${key}\u2717`;
1295
+ };
1296
+ const marks = [
1297
+ ...report.toolOutcomes.map((o) => mark(o.status, o.type, o.detail)),
1298
+ ...report.aiOutcomes.map((o) => mark(o.status, o.id, o.detail))
1299
+ ].join(" ");
1300
+ const line = `[checks] ${marks}`;
1301
+ if (line.length <= 600) return line;
1302
+ const toolPass = report.toolOutcomes.filter((o) => o.status === "pass").length;
1303
+ const toolNa = report.toolOutcomes.filter((o) => o.status === "not_applicable").length;
1304
+ const parts = [`\u5DE5\u5177\u68C0\u67E5 ${toolPass} \u9879\u901A\u8FC7${toolNa > 0 ? `\uFF0C${toolNa} \u9879 N/A` : ""}`];
1305
+ if (report.aiOutcomes.length > 0) {
1306
+ const aiPass = report.aiOutcomes.filter((o) => o.status === "pass").length;
1307
+ const aiNa = report.aiOutcomes.filter((o) => o.status === "not_applicable").length;
1308
+ parts.push(`\u5B8C\u6210\u5224\u5B9A\u5BA1\u8BA1 ${aiPass} \u9879\u901A\u8FC7${aiNa > 0 ? `\uFF0C${aiNa} \u9879 N/A` : ""}`);
1309
+ }
1310
+ return `[checks] ${parts.join("\uFF1B")}\uFF08\u9010\u9879\u660E\u7EC6\u89C1 history check-report\uFF09`;
1311
+ }
1312
+ function buildForceComment(forceReason, report, maxLen) {
1313
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
1314
+ const skipped = [
1315
+ ...report.toolOutcomes.filter((o) => o.status === "fail").map((o) => `${o.type}:${o.status}:${o.detail}`),
1316
+ ...report.aiOutcomes.filter((o) => o.status === "fail").map((o) => `ai:${o.id}:${o.status}:${o.detail}`)
1317
+ ];
1318
+ const full = [
1319
+ "[FORCE advance] \u7528\u6237\u6307\u4EE4\u539F\u6587\uFF1A" + forceReason,
1320
+ `\u88AB\u8DF3\u8FC7\u9879\u53CA\u5F53\u65F6\u72B6\u6001\uFF1A${skipped.length > 0 ? skipped.join("\uFF1B") : "\u65E0 fail \u9879"}`,
1321
+ `\u6267\u884C\u65F6\u70B9\uFF1A${ts} \u8282\u70B9 ${report.nodeId}`
1322
+ ].join("\n");
1323
+ if (maxLen === void 0 || full.length <= maxLen) return full;
1324
+ const types = [
1325
+ ...report.toolOutcomes.filter((o) => o.status === "fail").map((o) => o.type),
1326
+ ...report.aiOutcomes.filter((o) => o.status === "fail").map((o) => `ai:${o.id}`)
1327
+ ];
1328
+ const head = "[FORCE advance] \u7528\u6237\u6307\u4EE4\u539F\u6587\uFF1A" + forceReason;
1329
+ const time = `\u6267\u884C\u65F6\u70B9\uFF1A${ts} \u8282\u70B9 ${report.nodeId}`;
1330
+ const tail = "\uFF08\u660E\u7EC6\u8D85\u957F\u622A\u65AD\uFF0C\u9010\u9879\u89C1 history check-report\uFF09";
1331
+ let list = types.join(",");
1332
+ let compact = [head, `\u88AB\u8DF3\u8FC7 ${types.length} \u9879\uFF1A${list}${tail}`, time].join("\n");
1333
+ while (compact.length > maxLen && list.length > 0) {
1334
+ list = list.slice(0, Math.max(0, list.length - 40));
1335
+ compact = [head, `\u88AB\u8DF3\u8FC7 ${types.length} \u9879\uFF1A${list}\u2026${tail}`, time].join("\n");
1336
+ }
1337
+ if (compact.length > maxLen) {
1338
+ compact = [head, `\u88AB\u8DF3\u8FC7 ${types.length} \u9879${tail}`, time].join("\n");
1339
+ }
1340
+ if (compact.length > maxLen) {
1341
+ const headCut = Math.max(1, maxLen - time.length - 2);
1342
+ compact = `${head.slice(0, headCut)}\u2026
1343
+ ${time}`;
1344
+ }
1345
+ return compact.length <= maxLen ? compact : compact.slice(0, maxLen);
1346
+ }
1347
+
1348
+ // src/client/handlers/task.ts
678
1349
  async function handleCreateTask(client, input) {
679
1350
  return client.postJson("/api/tasks", input);
680
1351
  }
681
1352
  async function handleAdvance(client, taskId, note, summary, finalize) {
682
- return client.postJson(`/api/tasks/${taskId}/advance`, {
683
- ...note !== void 0 ? { note } : {},
1353
+ return advanceInner(client, taskId, note, summary, finalize, {});
1354
+ }
1355
+ async function handleAdvanceWithGate(client, taskId, gate, note, summary, finalize) {
1356
+ return advanceInner(client, taskId, note, summary, finalize, gate);
1357
+ }
1358
+ async function advanceInner(client, taskId, note, summary, finalize, gate) {
1359
+ const gateReport = await runNodeGate(client, taskId);
1360
+ if (!gateReport.success) return gateReport;
1361
+ const report = gateReport.data;
1362
+ const useForce = gate.force === true;
1363
+ let effectiveNote = note;
1364
+ if (!report.empty) {
1365
+ if (gateHasFailure(report)) {
1366
+ const rejectPayload = buildCheckReportPayload(
1367
+ report,
1368
+ useForce ? "force" : "reject",
1369
+ // force 留痕必须携带用户指令原文(验收 12)——note 走 force comment 模板(≤500,H3 截断保护)
1370
+ useForce && gate.forceReason !== void 0 ? buildForceComment(gate.forceReason, report, 500) : void 0
1371
+ );
1372
+ const reportRes = await postCheckReport(client, taskId, rejectPayload);
1373
+ const auditNote = reportRes.success ? "" : `
1374
+ \uFF08\u8B66\u793A\uFF1Acheck-report \u7559\u75D5\u5931\u8D25\uFF1A${reportRes.error.message}\uFF09`;
1375
+ if (!useForce) {
1376
+ const { message, hint } = buildGateFailMessage(report, reportRes.success);
1377
+ return fail({ message: `${message}${auditNote}`, hint, httpStatus: 0 });
1378
+ }
1379
+ if (!reportRes.success) {
1380
+ const w = `\uFF08\u8B66\u793A\uFF1Acheck-report \u7559\u75D5\u5931\u8D25\uFF1A${reportRes.error.message}\u2014\u2014\u672C\u6B21\u5F3A\u5236\u63A8\u8FDB\u5728 history \u65E0 checks_rejected \u8BB0\u5F55\uFF09`;
1381
+ effectiveNote = effectiveNote !== void 0 ? `${effectiveNote}
1382
+ ${w}` : w;
1383
+ }
1384
+ } else if (useForce) {
1385
+ const reportRes = await postCheckReport(
1386
+ client,
1387
+ taskId,
1388
+ buildCheckReportPayload(report, "force", buildForceComment(gate.forceReason ?? "", report, 500))
1389
+ );
1390
+ if (!reportRes.success) {
1391
+ const w = `\uFF08\u8B66\u793A\uFF1Acheck-report \u7559\u75D5\u5931\u8D25\uFF1A${reportRes.error.message}\u2014\u2014\u672C\u6B21\u5F3A\u5236\u63A8\u8FDB\u5728 history \u65E0 checks_rejected \u8BB0\u5F55\uFF09`;
1392
+ effectiveNote = effectiveNote !== void 0 ? `${effectiveNote}
1393
+ ${w}` : w;
1394
+ }
1395
+ } else {
1396
+ const passNote = buildPassNote(report);
1397
+ effectiveNote = note !== void 0 ? `${note}
1398
+ ${passNote}` : passNote;
1399
+ }
1400
+ } else if (useForce && gate.forceReason !== void 0) {
1401
+ effectiveNote = note !== void 0 ? `${note}
1402
+ [FORCE advance] ${gate.forceReason}` : `[FORCE advance] ${gate.forceReason}`;
1403
+ }
1404
+ const response = await client.postJson(`/api/tasks/${taskId}/advance`, {
1405
+ ...effectiveNote !== void 0 ? { note: effectiveNote } : {},
684
1406
  ...summary !== void 0 ? { summary } : {},
685
1407
  ...finalize ? { finalize: true } : {}
686
1408
  });
1409
+ if (!response.success) return response;
1410
+ if (useForce && response.data.status === "paused") {
1411
+ const comment = buildForceComment(gate.forceReason ?? "", report, 2e3);
1412
+ const approved = await handleApprove(client, taskId, "approved", comment);
1413
+ if (!approved.success) {
1414
+ return fail({
1415
+ message: `force advance \u5DF2\u5230\u6682\u505C\u70B9\uFF0C\u81EA\u52A8\u5BA1\u6279\u5931\u8D25\uFF1A${approved.error.message}`,
1416
+ hint: "\u4EFB\u52A1\u505C\u7559\u5728\u6682\u505C\u70B9\uFF1B\u8BF7\u4EBA\u5DE5\u5BA1\u6279\uFF08siming task approve\uFF09",
1417
+ httpStatus: 0
1418
+ });
1419
+ }
1420
+ if (approved.data.status === "rejected") {
1421
+ return fail({
1422
+ message: `force advance \u5BA1\u6279\u88AB\u9A73\u56DE\uFF1A${approved.data.guidance}`,
1423
+ hint: "\u4EFB\u52A1\u4FDD\u6301\u6682\u505C\uFF1B\u68C0\u67E5\u6682\u505C\u70B9\u5BA1\u6279\u7EA6\u675F",
1424
+ httpStatus: 0
1425
+ });
1426
+ }
1427
+ return ok2(approved.data);
1428
+ }
1429
+ return response;
1430
+ }
1431
+ async function postCheckReport(client, taskId, payload) {
1432
+ return client.postJson(`/api/tasks/${taskId}/check-report`, payload);
1433
+ }
1434
+ async function handleTaskCheck(client, taskId) {
1435
+ return runNodeGate(client, taskId);
687
1436
  }
688
1437
  async function handleApprove(client, taskId, decision, comment) {
689
1438
  return client.postJson(`/api/tasks/${taskId}/approve`, {
@@ -735,14 +1484,35 @@ function handleDocAcceptanceAdd(client, taskId, text) {
735
1484
  function handleDocNonGoalAdd(client, taskId, text) {
736
1485
  return client.postJson(`/api/tasks/${taskId}/doc/non-goal`, { text });
737
1486
  }
738
- function handleRecordSummarySet(client, taskId, nodeId, summary) {
739
- return client.patchJson(`/api/tasks/${taskId}/records/${nodeId}/summary`, { summary });
1487
+ function handleRecordSummarySet(client, taskId, nodeId, fields) {
1488
+ return client.patchJson(
1489
+ `/api/tasks/${taskId}/records/${nodeId}/summary`,
1490
+ {
1491
+ ...fields.summary !== void 0 ? { summary: fields.summary } : {},
1492
+ ...fields.baseBranch !== void 0 ? { baseBranch: fields.baseBranch } : {},
1493
+ ...fields.verifiedCommit !== void 0 ? { verifiedCommit: fields.verifiedCommit } : {},
1494
+ ...fields.fixesApplied !== void 0 ? { fixesApplied: fields.fixesApplied } : {},
1495
+ ...fields.testReport !== void 0 ? { testReport: fields.testReport } : {},
1496
+ ...fields.deferredIssues !== void 0 ? { deferredIssues: fields.deferredIssues } : {},
1497
+ ...fields.evidenceFiles !== void 0 ? { evidenceFiles: fields.evidenceFiles } : {}
1498
+ }
1499
+ );
740
1500
  }
741
- function handleRecordCheckAdd(client, taskId, nodeId, item, passed) {
742
- return client.postJson(`/api/tasks/${taskId}/records/${nodeId}/checks`, { item, passed });
1501
+ function handleRecordCheckAdd(client, taskId, nodeId, item, passed, options) {
1502
+ return client.postJson(`/api/tasks/${taskId}/records/${nodeId}/checks`, {
1503
+ item,
1504
+ passed,
1505
+ ...options?.refId !== void 0 ? { refId: options.refId } : {},
1506
+ ...options?.source !== void 0 ? { source: options.source } : {}
1507
+ });
743
1508
  }
744
- function handleRecordCheckPatch(client, taskId, nodeId, checkId, passed) {
745
- return client.patchJson(`/api/tasks/${taskId}/records/${nodeId}/checks/${checkId}`, { passed });
1509
+ function handleRecordCheckPatch(client, taskId, nodeId, checkId, patch) {
1510
+ return client.patchJson(`/api/tasks/${taskId}/records/${nodeId}/checks/${checkId}`, {
1511
+ ...patch.passed !== void 0 ? { passed: patch.passed } : {},
1512
+ ...patch.item !== void 0 ? { item: patch.item } : {},
1513
+ ...patch.refId !== void 0 ? { refId: patch.refId } : {},
1514
+ ...patch.source !== void 0 ? { source: patch.source } : {}
1515
+ });
746
1516
  }
747
1517
  var ARTIFACT_CONTENT_LIMIT = 2e5;
748
1518
  function handleRecordArtifactAdd(client, taskId, nodeId, type, path, note, content) {
@@ -1114,7 +1884,7 @@ async function handleUpgradeApply(client, templateId, decisions) {
1114
1884
 
1115
1885
  // src/client/handlers/auth.ts
1116
1886
  import {
1117
- existsSync as existsSync5,
1887
+ existsSync as existsSync6,
1118
1888
  readFileSync as readFileSync7,
1119
1889
  writeFileSync as writeFileSync4,
1120
1890
  renameSync as renameSync3,
@@ -1206,7 +1976,7 @@ function writeWorkspaceLayer(token, kind, opts) {
1206
1976
  const devJsonPath = join5(dir, ".siming", "dev.json");
1207
1977
  let config = {};
1208
1978
  let hadIsolationKeys = false;
1209
- if (existsSync5(devJsonPath)) {
1979
+ if (existsSync6(devJsonPath)) {
1210
1980
  try {
1211
1981
  config = JSON.parse(readFileSync7(devJsonPath, "utf-8"));
1212
1982
  hadIsolationKeys = "serverPort" in config || "dbPrefix" in config;
@@ -1263,6 +2033,9 @@ function readStdinLine(stream) {
1263
2033
  async function handleCreateIssue(client, input) {
1264
2034
  return client.postJson("/api/issues", input);
1265
2035
  }
2036
+ async function handleDoneIssue(client, issueId, note) {
2037
+ return client.postJson(`/api/issues/${issueId}/done`, note !== void 0 ? { note } : {});
2038
+ }
1266
2039
  async function handleListIssues(client, filters) {
1267
2040
  const params = new URLSearchParams();
1268
2041
  if (filters.status) params.set("status", filters.status);
@@ -1436,6 +2209,8 @@ var taskArgs = {
1436
2209
  "context",
1437
2210
  "node",
1438
2211
  "advance",
2212
+ // 门禁只读预检(T202609230001:advance 前自查节点门禁,零写入)
2213
+ "check",
1439
2214
  "approve",
1440
2215
  "pause",
1441
2216
  "resume",
@@ -1478,10 +2253,13 @@ var taskArgs = {
1478
2253
  view: hostNeutralEnum(z3.enum(["prompt", "skills", "basic", "full"])).optional(),
1479
2254
  nodeId: z3.string().optional(),
1480
2255
  // advance / record-set(note 写 history;summary 写 nodeRecords,两位独立);
1481
- // finalize 为 advance 收尾确认(终节点终态化必需,中间节点无副作用——T202609130009)
2256
+ // finalize 为 advance 收尾确认(终节点终态化必需,中间节点无副作用——T202609130009);
2257
+ // force/forceReason 成对(T202609230001:门禁强制推进,仅凭用户明示指令,AI 禁自决)
1482
2258
  note: z3.string().optional(),
1483
2259
  summary: z3.string().optional(),
1484
2260
  finalize: z3.boolean().optional(),
2261
+ force: z3.boolean().optional(),
2262
+ forceReason: z3.string().optional(),
1485
2263
  // approve
1486
2264
  decision: hostNeutralEnum(z3.enum(["approved", "rejected"])).optional(),
1487
2265
  comment: z3.string().optional(),
@@ -1493,6 +2271,21 @@ var taskArgs = {
1493
2271
  item: z3.string().optional(),
1494
2272
  checkId: z3.string().optional(),
1495
2273
  passed: z3.boolean().optional(),
2274
+ // T202609230001 F1:结构化记录字段(record-set)+ 检查条目留痕锚(check-add/patch)
2275
+ baseBranch: z3.string().optional(),
2276
+ verifiedCommit: z3.string().optional(),
2277
+ fixesApplied: z3.number().int().nonnegative().optional(),
2278
+ testReport: z3.object({
2279
+ command: z3.string(),
2280
+ passed: z3.number().int().nonnegative(),
2281
+ failed: z3.number().int().nonnegative(),
2282
+ // 与 core TestReportSchema 对齐:skipped 必填(缺省语义由调用方显式补 0,禁 optional 漂移)
2283
+ skipped: z3.number().int().nonnegative()
2284
+ }).optional(),
2285
+ deferredIssues: z3.array(z3.string()).optional(),
2286
+ evidenceFiles: z3.array(z3.string()).optional(),
2287
+ refId: z3.string().optional(),
2288
+ source: hostNeutralEnum(z3.enum(["self", "tool"])).optional(),
1496
2289
  artifactType: hostNeutralEnum(z3.enum(ARTIFACT_TYPES2)).optional(),
1497
2290
  path: z3.string().optional(),
1498
2291
  file: z3.string().optional(),
@@ -1509,7 +2302,14 @@ var taskArgs = {
1509
2302
  trackNote: z3.string().optional(),
1510
2303
  text: z3.string().optional(),
1511
2304
  // 批量形态(T202609010002):与对应单条参数二选一;元素字段级校验归 server 分区步(部分失败保留成功条目)
1512
- checkItems: z3.array(z3.object({ item: z3.string(), passed: z3.boolean().optional() })).optional(),
2305
+ checkItems: z3.array(
2306
+ z3.object({
2307
+ item: z3.string(),
2308
+ passed: z3.boolean().optional(),
2309
+ refId: z3.string().optional(),
2310
+ source: hostNeutralEnum(z3.enum(["self", "tool"])).optional()
2311
+ })
2312
+ ).optional(),
1513
2313
  artifactEntries: z3.array(
1514
2314
  z3.object({
1515
2315
  artifactType: z3.enum(ARTIFACT_TYPES2),
@@ -1538,6 +2338,8 @@ var issueArgs = {
1538
2338
  // create
1539
2339
  title: z3.string().optional(),
1540
2340
  description: z3.string().optional(),
2341
+ /** create 必填(T202609200001):人类审批原话逐字(1-500 字)——per-action 必填以 ISSUE_FIELD_SPECS.create.required 为权威 */
2342
+ approved: z3.string().optional(),
1541
2343
  // 公共:create 缺省按工作区 defaultProject(key|id 双形态通道层归一,失效报错)/ query 过滤(空/未传不过滤)
1542
2344
  projectId: z3.string().optional(),
1543
2345
  // query:view 缺省 open(有效未跟进完);与 REST 词汇统一(status/taskId 指定时切对应过滤)
@@ -1645,7 +2447,8 @@ var TASK_FIELD_SPECS = {
1645
2447
  query: { required: [], optional: ["taskId", "status", "track", "projectId", "page", "limit"] },
1646
2448
  context: { required: ["taskId"], optional: ["view"] },
1647
2449
  node: { required: ["taskId", "view"], optional: ["nodeId"] },
1648
- advance: { required: ["taskId"], optional: ["note", "summary", "finalize"] },
2450
+ advance: { required: ["taskId"], optional: ["note", "summary", "finalize", "force", "forceReason"] },
2451
+ check: { required: ["taskId"], optional: [] },
1649
2452
  approve: { required: ["taskId", "decision"], optional: ["comment"] },
1650
2453
  pause: { required: ["taskId"], optional: ["reason"] },
1651
2454
  resume: { required: ["taskId"], optional: ["resumeNote"] },
@@ -1653,9 +2456,12 @@ var TASK_FIELD_SPECS = {
1653
2456
  reopen: { required: ["taskId", "reason"], optional: [] },
1654
2457
  history: { required: ["taskId"], optional: [] },
1655
2458
  instance: { required: ["taskId"], optional: [] },
1656
- "record-set": { required: ["taskId", "node", "summary"], optional: [] },
1657
- "record-check-add": { required: ["taskId", "node"], optional: ["item", "passed", "checkItems"] },
1658
- "record-check-patch": { required: ["taskId", "node", "checkId"], optional: ["passed"] },
2459
+ "record-set": {
2460
+ required: ["taskId", "node"],
2461
+ optional: ["summary", "baseBranch", "verifiedCommit", "fixesApplied", "testReport", "deferredIssues", "evidenceFiles"]
2462
+ },
2463
+ "record-check-add": { required: ["taskId", "node"], optional: ["item", "passed", "checkItems", "refId", "source"] },
2464
+ "record-check-patch": { required: ["taskId", "node", "checkId"], optional: ["passed", "item", "refId", "source"] },
1659
2465
  "record-artifact-add": {
1660
2466
  required: ["taskId", "node"],
1661
2467
  optional: ["artifactType", "path", "file", "content", "note", "artifactEntries"]
@@ -1675,7 +2481,7 @@ var TASK_FIELD_SPECS = {
1675
2481
  "archnote-add": { required: ["taskId"], optional: ["text", "textItems"] }
1676
2482
  };
1677
2483
  var ISSUE_FIELD_SPECS = {
1678
- create: { required: ["title"], optional: ["description", "projectId"] },
2484
+ create: { required: ["title", "approved"], optional: ["description", "projectId"] },
1679
2485
  query: { required: [], optional: ["view", "status", "taskId", "projectId", "page", "limit"] }
1680
2486
  };
1681
2487
  function createMcpServer() {
@@ -1683,7 +2489,7 @@ function createMcpServer() {
1683
2489
  const client = createClient(void 0, "mcp");
1684
2490
  server.tool(
1685
2491
  "siming_task",
1686
- "task \u8D44\u6E90\u57DF\u805A\u5408\u5DE5\u5177\uFF08\u4EFB\u52A1\u5168\u64CD\u4F5C\u7ECF\u672C\u5DE5\u5177 + action \u53C2\u6570\uFF09\u3002action \u5FC5\u586B\uFF1B\u8C03\u7528\u65B9\u5E94\u5728 fieldMask \u4E2D\u5217\u51FA\u672C\u6B21\u771F\u5B9E\u63D0\u4F9B\u7684\u53EF\u9009\u5B57\u6BB5\uFF0Cmask \u5916\u7531\u5BBF\u4E3B\u81EA\u52A8\u586B\u5145\u7684\u7A7A\u4E32\u3001\u7A7A\u6570\u7EC4\u3001false\u30010 \u5747\u5FFD\u7565\uFF0Cmask \u5185\u7A7A\u6570\u7EC4/false \u8868\u793A\u663E\u5F0F\u6E05\u7A7A\u6216\u5173\u95ED\uFF1B\u5404 action \u56FA\u5B9A\u5FC5\u586B\u5B57\u6BB5\u65E0\u9700\u5199\u5165 mask\u3002\u5176\u4F59\u53C2\u6570\u5168\u53EF\u9009\u3001\u6309 action \u8FD0\u884C\u65F6\u6821\u9A8C\u5FC5\u586B\uFF08\u7F3A\u5931\u8FD4\u56DE\u5931\u8D25\u4E09\u8981\u7D20\uFF1A\u5BF9\u8C61+\u6839\u56E0+\u4FEE\u590D\u6307\u5F15\uFF09\u3002projectId \u4E3A key|id \u53CC\u5F62\u6001\uFF0C\u901A\u9053\u5C42\u81EA\u52A8\u5F52\u4E00\u4E3A 24-hex id\uFF1B\u663E\u5F0F\u6307\u5B9A\u89E3\u6790\u5931\u8D25\u62A5\u9519\u4E0D\u515C\u5E95\uFF1B\u7F3A\u7701\u8BED\u4E49\u6309 action\uFF1Acreate \u843D\u9ED8\u8BA4\u9879\u76EE\u94FE\uFF08\u5DE5\u4F5C\u533A defaultProject \u5931\u6548\u62A5\u9519\uFF0C\u65E0\u914D\u7F6E\u4E0D\u4F20\u7531 server \u6309\u6A21\u677F\u5F52\u5C5E\u515C\u5E95\uFF09\uFF0Cquery \u4E0D\u8FC7\u6EE4\uFF08\u5168\u5C40\u67E5\u8BE2\uFF09\u3002\u5199\u64CD\u4F5C\uFF08create/doc-*/record-*/archnote-add/pause/resume/cancel\uFF09\u54CD\u5E94\u4E3A\u8F7B\u91CF\u56DE\u6267\uFF1A{taskId, updated(\u89E6\u53CA\u5B57\u6BB5\u8DEF\u5F84), entry(\u65B0\u6761\u76EE id+kind\uFF0C\u4F9B\u540E\u7EED patch \u5B9A\u4F4D), echo(\u5199\u5165\u5185\u5BB9\u56DE\u663E), updatedAt, task(\u4EFB\u52A1\u8FDB\u5EA6\u6458\u8981 progress: {completed,total})}\u2014\u2014\u4E0D\u8FD4\u56DE\u5B8C\u6574\u4EFB\u52A1\u5B9E\u4F53\u3002action \u6E05\u5355\uFF08\u62EC\u53F7\u5185\u4E3A\u8BE5 action \u5FC5\u586B\u53C2\u6570\uFF09\uFF1Acreate(templateId,taskTitle,track\uFF1B\u53EF\u9009 type=feature|bugfix|ui-tweak|research\u3001skipNodes\u3001projectId\uFF1BtemplateId \u652F\u6301 code|id \u53CC\u5F62\u6001\u2014\u2014code \u4E3A\u6A21\u677F\u4E1A\u52A1\u6807\u8BC6\uFF08\u9879\u76EE\u5185\u552F\u4E00\u3001kebab-case \u5FC5\u542B\u8FDE\u5B57\u7B26\uFF0C\u5982 full-workflow\uFF09\uFF0C\u63A8\u8350\u5F15\u7528\u5F62\u6001\uFF0C\u5BFB\u5740\u987B\u914D projectId \u6307\u5B9A\u9879\u76EE\u57DF\uFF1B24-hex \u6570\u636E\u5E93 id \u76F4\u67E5\u517C\u5BB9\uFF1Bcode \u5F62\u6001\u4E0D\u5B58\u5728\u65F6\u9519\u8BEF\u542B candidates \u76F8\u8FD1\u5019\u9009\u6E05\u5355\uFF08id/code/name \u22645 \u6761\uFF09\uFF1B\u54CD\u5E94 {task, firstNode}\u2014\u2014firstNode \u5373\u9996\u8282\u70B9\u5B8C\u6574\u8D44\u6599\u5305\uFF0C\u514D\u8C03 context)\uFF1Bquery(taskId \u8FD4\u56DE\u5355\u4EFB\u52A1\u8BE6\u60C5\uFF0C\u7F3A\u7701\u8FD4\u56DE\u5217\u8868\uFF0C\u53EF\u9009 status/track/projectId/page/limit)\uFF1Bcontext(taskId\uFF1B\u53EF\u9009 view=basic|full\u2014\u2014basic \u7F3A\u7701\u4E0D\u643A\u5E26 artifact \u5168\u6587\u5FEB\u7167\uFF0Cfull \u65AD\u70B9\u7EED\u8DD1\u9700\u5386\u53F2\u4EA7\u7269\u5168\u6587\u65F6\u7528\u3002\u26A0 \u514D\u91CD\u8BFB\uFF1Aadvance/approve \u6D41\u8F6C\u54CD\u5E94\u5DF2\u542B\u4E0B\u4E00\u8282\u70B9\u5B8C\u6574\u8D44\u6599\u5305\uFF0C\u540C\u4F1A\u8BDD\u5185\u6D41\u8F6C\u540E\u76F4\u63A5\u5F00\u5DE5\u3001\u65E0\u9700\u518D\u8C03 context\uFF1B\u4EC5\u65B0\u4F1A\u8BDD/\u65AD\u70B9\u7EED\u8DD1\u8C03\u7528)\uFF1Bnode(taskId,view=prompt|skills\uFF1BnodeId \u7F3A\u7701\u7528\u5F53\u524D\u8282\u70B9\uFF1B\u26A0 view \u503C\u968F action \u4E0D\u540C\uFF1Anode\u2192prompt|skills\uFF0Ccontext\u2192basic|full\uFF0C\u9519\u914D\u62A5\u9519)\uFF1Badvance(taskId\uFF1Bnote \u5199 history\u3001summary \u5199 nodeRecords\uFF0C\u4E24\u4F4D\u72EC\u7ACB\uFF1B\u54CD\u5E94\u5DF2\u542B\u4E0B\u4E00\u8282\u70B9\u8D44\u6599\u5305\uFF0C\u540C\u4F1A\u8BDD\u514D\u91CD\u8BFB context)\uFF1Bapprove(taskId,decision=approved|rejected\uFF1B\u53EF\u9009 comment\uFF1B\u54CD\u5E94\u5DF2\u542B\u4E0B\u4E00\u8282\u70B9\u8D44\u6599\u5305\uFF0C\u540C\u4F1A\u8BDD\u514D\u91CD\u8BFB context)\uFF1Bpause(taskId\uFF1B\u53EF\u9009 reason)\uFF1Bresume(taskId\uFF1B\u53EF\u9009 resumeNote)\uFF1Bcancel(taskId\uFF1B\u53EF\u9009 reason 1-500 \u5B57\u7B26\uFF1B\u26A0 \u7EC8\u6001\u4E0D\u53EF\u9006\u2014\u2014active/paused \u53EF\u53D6\u6D88\uFF0Ccompleted/cancelled \u62D2\u7EDD\u5E76\u8FD4\u56DE TASK_TERMINATED\uFF1B\u53D6\u6D88\u540E\u7ECF\u672C\u5DE5\u5177\u7684\u6D41\u8F6C\u4E0E\u8BB0\u5F55\u5199\u64CD\u4F5C\u88AB\u62D2)\uFF1Bhistory(taskId)\uFF1Binstance(taskId\u2014\u2014\u67E5\u770B\u4EFB\u52A1 DAG \u5B9E\u4F8B\u5FEB\u7167\uFF1A\u521B\u5EFA\u4EFB\u52A1\u65F6\u51BB\u7ED3\u7684\u6A21\u677F\u5B9E\u4F8B\uFF0C\u6A21\u677F\u540E\u7EED\u53D8\u66F4\u4E0D\u56DE\u6EAF)\u3002\u8BB0\u5F55\u5199\u5165\uFF1Arecord-set(taskId,node,summary)\uFF1Brecord-check-add(taskId,node,item\uFF1B\u53EF\u9009 passed \u7F3A\u7701 true\uFF1B\u56DE\u6267 entry.id \u4F9B record-check-patch \u5B9A\u4F4D)\uFF1Brecord-check-patch(taskId,node,checkId\uFF1B\u53EF\u9009 passed)\uFF1Brecord-artifact-add(taskId,node,artifactType=prd|tech|code|test|doc|other\uFF1Bpath \u7EAF\u5F15\u7528 / file \u8BFB\u672C\u673A\u6587\u4EF6 / content \u76F4\u4F20\u5168\u6587\u2014\u2014file \u4E0E content\u3001file \u4E0E path \u4E92\u65A5\uFF0Ccontent \u53EF\u4E0E path \u6210\u5BF9\u6216\u5355\u72EC\uFF0C\u5355\u72EC\u65F6\u767B\u8BB0\u503C\u5408\u6210 mcp-inline/<type>\uFF1B\u53EF\u9009 note)\uFF1Brecord-confirm(taskId,node,quote\u2014\u2014\u6682\u505C\u70B9\u7528\u6237\u786E\u8BA4\u539F\u6587\u9010\u5B57)\uFF1Brecord-decision-add(taskId,node,topic,decisionText)\uFF1Brecord-review-set(taskId,node,verdict=pass|fail,rounds,critical)\u3002\u9700\u6C42\u6587\u6863\uFF1Adoc-set(taskId\uFF1Bwhat/why/trackNote \u81F3\u5C11\u4F20\u4E00)\uFF1Bdoc-acceptance-add(taskId,text)\uFF1Bdoc-non-goal-add(taskId,text)\u3002\u7D20\u6750\uFF1Aarchnote-add(taskId,text)\u3002\u6279\u91CF\u5F62\u6001\uFF08\u63A8\u8350\uFF0C\u516D\u7C7B\u540C\u6784\uFF09\uFF1Arecord-check-add \u4F20 checkItems=[{item,passed?}]\u3001record-artifact-add \u4F20 artifactEntries=[{artifactType,path?/file?/content?,note?}]\u3001record-decision-add \u4F20 decisionEntries=[{topic,decisionText}]\u3001doc-acceptance-add/doc-non-goal-add/archnote-add \u4F20 textItems=[string]\u2014\u2014\u6279\u91CF\u53C2\u6570\u4E0E\u5355\u6761\u53C2\u6570\u4E8C\u9009\u4E00\uFF0C\u4E00\u6B21\u8C03\u7528\u5199\u591A\u6761\uFF08\u4E0A\u9650 50 \u6761/\u6279\uFF0Cartifact \u5168\u6587\u5FEB\u7167\u603B\u91CF \u22642M \u5B57\u7B26/\u6279\uFF09\uFF1B\u90E8\u5206\u5931\u8D25\u4FDD\u7559\u6210\u529F\u6761\u76EE\u5E76\u9010\u6761\u62A5\u544A\uFF08failures[].index=\u539F\u59CB\u63D0\u4EA4\u5E8F\uFF0C\u53EA\u91CD\u8BD5\u5931\u8D25\u9879\uFF09\uFF0Capplied=0 \u6574\u4F53\u62A5\u5931\u8D25\u3002",
2492
+ "task \u8D44\u6E90\u57DF\u805A\u5408\u5DE5\u5177\uFF08\u4EFB\u52A1\u5168\u64CD\u4F5C\u7ECF\u672C\u5DE5\u5177 + action \u53C2\u6570\uFF09\u3002action \u5FC5\u586B\uFF1B\u8C03\u7528\u65B9\u5E94\u5728 fieldMask \u4E2D\u5217\u51FA\u672C\u6B21\u771F\u5B9E\u63D0\u4F9B\u7684\u53EF\u9009\u5B57\u6BB5\uFF0Cmask \u5916\u7531\u5BBF\u4E3B\u81EA\u52A8\u586B\u5145\u7684\u7A7A\u4E32\u3001\u7A7A\u6570\u7EC4\u3001false\u30010 \u5747\u5FFD\u7565\uFF0Cmask \u5185\u7A7A\u6570\u7EC4/false \u8868\u793A\u663E\u5F0F\u6E05\u7A7A\u6216\u5173\u95ED\uFF1B\u5404 action \u56FA\u5B9A\u5FC5\u586B\u5B57\u6BB5\u65E0\u9700\u5199\u5165 mask\u3002\u5176\u4F59\u53C2\u6570\u5168\u53EF\u9009\u3001\u6309 action \u8FD0\u884C\u65F6\u6821\u9A8C\u5FC5\u586B\uFF08\u7F3A\u5931\u8FD4\u56DE\u5931\u8D25\u4E09\u8981\u7D20\uFF1A\u5BF9\u8C61+\u6839\u56E0+\u4FEE\u590D\u6307\u5F15\uFF09\u3002projectId \u4E3A key|id \u53CC\u5F62\u6001\uFF0C\u901A\u9053\u5C42\u81EA\u52A8\u5F52\u4E00\u4E3A 24-hex id\uFF1B\u663E\u5F0F\u6307\u5B9A\u89E3\u6790\u5931\u8D25\u62A5\u9519\u4E0D\u515C\u5E95\uFF1B\u7F3A\u7701\u8BED\u4E49\u6309 action\uFF1Acreate \u843D\u9ED8\u8BA4\u9879\u76EE\u94FE\uFF08\u5DE5\u4F5C\u533A defaultProject \u5931\u6548\u62A5\u9519\uFF0C\u65E0\u914D\u7F6E\u4E0D\u4F20\u7531 server \u6309\u6A21\u677F\u5F52\u5C5E\u515C\u5E95\uFF09\uFF0Cquery \u4E0D\u8FC7\u6EE4\uFF08\u5168\u5C40\u67E5\u8BE2\uFF09\u3002\u5199\u64CD\u4F5C\uFF08create/doc-*/record-*/archnote-add/pause/resume/cancel\uFF09\u54CD\u5E94\u4E3A\u8F7B\u91CF\u56DE\u6267\uFF1A{taskId, updated(\u89E6\u53CA\u5B57\u6BB5\u8DEF\u5F84), entry(\u65B0\u6761\u76EE id+kind\uFF0C\u4F9B\u540E\u7EED patch \u5B9A\u4F4D), echo(\u5199\u5165\u5185\u5BB9\u56DE\u663E), updatedAt, task(\u4EFB\u52A1\u8FDB\u5EA6\u6458\u8981 progress: {completed,total})}\u2014\u2014\u4E0D\u8FD4\u56DE\u5B8C\u6574\u4EFB\u52A1\u5B9E\u4F53\u3002action \u6E05\u5355\uFF08\u62EC\u53F7\u5185\u4E3A\u8BE5 action \u5FC5\u586B\u53C2\u6570\uFF09\uFF1Acreate(templateId,taskTitle,track\uFF1B\u53EF\u9009 type=feature|bugfix|ui-tweak|research\u3001skipNodes\u3001projectId\uFF1BtemplateId \u652F\u6301 code|id \u53CC\u5F62\u6001\u2014\u2014code \u4E3A\u6A21\u677F\u4E1A\u52A1\u6807\u8BC6\uFF08\u9879\u76EE\u5185\u552F\u4E00\u3001kebab-case \u5FC5\u542B\u8FDE\u5B57\u7B26\uFF0C\u5982 full-workflow\uFF09\uFF0C\u63A8\u8350\u5F15\u7528\u5F62\u6001\uFF0C\u5BFB\u5740\u987B\u914D projectId \u6307\u5B9A\u9879\u76EE\u57DF\uFF1B24-hex \u6570\u636E\u5E93 id \u76F4\u67E5\u517C\u5BB9\uFF1Bcode \u5F62\u6001\u4E0D\u5B58\u5728\u65F6\u9519\u8BEF\u542B candidates \u76F8\u8FD1\u5019\u9009\u6E05\u5355\uFF08id/code/name \u22645 \u6761\uFF09\uFF1B\u54CD\u5E94 {task, firstNode}\u2014\u2014firstNode \u5373\u9996\u8282\u70B9\u5B8C\u6574\u8D44\u6599\u5305\uFF0C\u514D\u8C03 context)\uFF1Bquery(taskId \u8FD4\u56DE\u5355\u4EFB\u52A1\u8BE6\u60C5\uFF0C\u7F3A\u7701\u8FD4\u56DE\u5217\u8868\uFF0C\u53EF\u9009 status/track/projectId/page/limit)\uFF1Bcontext(taskId\uFF1B\u53EF\u9009 view=basic|full\u2014\u2014basic \u7F3A\u7701\u4E0D\u643A\u5E26 artifact \u5168\u6587\u5FEB\u7167\uFF0Cfull \u65AD\u70B9\u7EED\u8DD1\u9700\u5386\u53F2\u4EA7\u7269\u5168\u6587\u65F6\u7528\u3002\u26A0 \u514D\u91CD\u8BFB\uFF1Aadvance/approve \u6D41\u8F6C\u54CD\u5E94\u5DF2\u542B\u4E0B\u4E00\u8282\u70B9\u5B8C\u6574\u8D44\u6599\u5305\uFF0C\u540C\u4F1A\u8BDD\u5185\u6D41\u8F6C\u540E\u76F4\u63A5\u5F00\u5DE5\u3001\u65E0\u9700\u518D\u8C03 context\uFF1B\u4EC5\u65B0\u4F1A\u8BDD/\u65AD\u70B9\u7EED\u8DD1\u8C03\u7528)\uFF1Bnode(taskId,view=prompt|skills\uFF1BnodeId \u7F3A\u7701\u7528\u5F53\u524D\u8282\u70B9\uFF1B\u26A0 view \u503C\u968F action \u4E0D\u540C\uFF1Anode\u2192prompt|skills\uFF0Ccontext\u2192basic|full\uFF0C\u9519\u914D\u62A5\u9519)\uFF1Badvance(taskId\uFF1Bnote \u5199 history\u3001summary \u5199 nodeRecords\uFF0C\u4E24\u4F4D\u72EC\u7ACB\uFF1B\u54CD\u5E94\u5DF2\u542B\u4E0B\u4E00\u8282\u70B9\u8D44\u6599\u5305\uFF0C\u540C\u4F1A\u8BDD\u514D\u91CD\u8BFB context\u3002\u26A0 T202609230001 \u8282\u70B9\u95E8\u7981\uFF1A\u914D\u7F6E\u4E86 toolChecks/aiChecks \u7684\u8282\u70B9\u63A8\u8FDB\u524D\u81EA\u52A8\u6267\u884C\u68C0\u67E5\u2014\u2014fail \u4F1A\u88AB\u6253\u56DE\u5E76\u8FD4\u56DE\u9010\u9879\u4FEE\u590D\u6307\u5F15\uFF08\u7ED3\u679C\u5DF2\u5199 history checks_rejected\uFF09\uFF0C\u6309\u6307\u5F15\u4FEE\u590D\u6216\u83B7\u7528\u6237\u8C41\u514D\u7559\u75D5\u540E\u91CD\u63A8\uFF1B\u975E\u4EBA\u5DE5\u5BA1\u6279\u8282\u70B9\u7981\u6B62\u56E0\u95E8\u7981\u4E2D\u65AD\u4EFB\u52A1\uFF08\u6253\u56DE\u4FE1\u606F\u5373\u7EE7\u7EED\u4F9D\u636E\uFF09\u3002force+forceReason \u6210\u5BF9\u63D0\u4F9B\u65F6\u53EF\u8D8A\u8FC7 fail \u5F3A\u5236\u63A8\u8FDB\u2014\u2014\u4EC5\u51ED\u7528\u6237\u660E\u793A\u6307\u4EE4\uFF0CAI \u7981\u6B62\u81EA\u51B3\uFF0C\u7406\u7531\u539F\u6587\u843D history \u5BA1\u8BA1)\uFF1Bcheck(taskId\uFF1B\u95E8\u7981\u53EA\u8BFB\u9884\u68C0\u2014\u2014\u9010\u9879\u62A5\u544A\u4E0D\u5199\u4EFB\u4F55\u72B6\u6001\uFF0Cadvance \u524D\u81EA\u67E5\u7528)\uFF1Bapprove(taskId,decision=approved|rejected\uFF1B\u53EF\u9009 comment\uFF1B\u54CD\u5E94\u5DF2\u542B\u4E0B\u4E00\u8282\u70B9\u8D44\u6599\u5305\uFF0C\u540C\u4F1A\u8BDD\u514D\u91CD\u8BFB context)\uFF1Bpause(taskId\uFF1B\u53EF\u9009 reason)\uFF1Bresume(taskId\uFF1B\u53EF\u9009 resumeNote)\uFF1Bcancel(taskId\uFF1B\u53EF\u9009 reason 1-500 \u5B57\u7B26\uFF1B\u26A0 \u7EC8\u6001\u4E0D\u53EF\u9006\u2014\u2014active/paused \u53EF\u53D6\u6D88\uFF0Ccompleted/cancelled \u62D2\u7EDD\u5E76\u8FD4\u56DE TASK_TERMINATED\uFF1B\u53D6\u6D88\u540E\u7ECF\u672C\u5DE5\u5177\u7684\u6D41\u8F6C\u4E0E\u8BB0\u5F55\u5199\u64CD\u4F5C\u88AB\u62D2)\uFF1Bhistory(taskId)\uFF1Binstance(taskId\u2014\u2014\u67E5\u770B\u4EFB\u52A1 DAG \u5B9E\u4F8B\u5FEB\u7167\uFF1A\u521B\u5EFA\u4EFB\u52A1\u65F6\u51BB\u7ED3\u7684\u6A21\u677F\u5B9E\u4F8B\uFF0C\u6A21\u677F\u540E\u7EED\u53D8\u66F4\u4E0D\u56DE\u6EAF)\u3002\u8BB0\u5F55\u5199\u5165\uFF1Arecord-set(taskId,node\uFF1Bsummary \u4E0E\u516D\u4E2A\u7ED3\u6784\u5316\u5B57\u6BB5\uFF08baseBranch/verifiedCommit/fixesApplied/testReport/deferredIssues/evidenceFiles\uFF09\u81F3\u5C11\u5176\u4E00\u2014\u2014\u7ED3\u6784\u5316\u5B57\u6BB5\u6309\u8282\u70B9 prompt\u300C\u7ED3\u6784\u5316\u5B57\u6BB5\u5199\u5165\u8981\u6C42\u300D\u9010\u5B57\u6BB5\u843D\u5E93\uFF0CPATCH \u8BED\u4E49=\u4EC5\u66F4\u65B0\u63D0\u4F9B\u5B57\u6BB5)\uFF1Brecord-check-add(taskId,node,item\uFF1B\u53EF\u9009 passed \u7F3A\u7701 true\u3001refId=aiChecks \u81EA\u68C0\u7559\u75D5\u951A\uFF08\u5BA1\u8BA1\u8BA4 refId===aiCheck.id \u4E14 passed=true\uFF0C\u7F3A\u7559\u75D5\u7684\u5B8C\u6210\u5224\u5B9A\u5BA1\u8BA1 fail\uFF09\u3001source=self|tool \u672C\u7248\u5DE5\u5177\u7F3A\u7701\u4E0D\u5199\uFF1B\u6279\u91CF checkItems \u5143\u7D20\u540C\u6784\uFF1B\u56DE\u6267 entry.id \u4F9B record-check-patch \u5B9A\u4F4D)\uFF1Brecord-check-patch(taskId,node,checkId\uFF1B\u53EF\u9009 passed/item/refId/source\uFF0C\u4EC5\u66F4\u65B0\u63D0\u4F9B\u5B57\u6BB5)\uFF1Brecord-artifact-add(taskId,node,artifactType=prd|tech|code|test|doc|other\uFF1Bpath \u7EAF\u5F15\u7528 / file \u8BFB\u672C\u673A\u6587\u4EF6 / content \u76F4\u4F20\u5168\u6587\u2014\u2014file \u4E0E content\u3001file \u4E0E path \u4E92\u65A5\uFF0Ccontent \u53EF\u4E0E path \u6210\u5BF9\u6216\u5355\u72EC\uFF0C\u5355\u72EC\u65F6\u767B\u8BB0\u503C\u5408\u6210 mcp-inline/<type>\uFF1B\u53EF\u9009 note)\uFF1Brecord-confirm(taskId,node,quote\u2014\u2014\u6682\u505C\u70B9\u7528\u6237\u786E\u8BA4\u539F\u6587\u9010\u5B57)\uFF1Brecord-decision-add(taskId,node,topic,decisionText)\uFF1Brecord-review-set(taskId,node,verdict=pass|fail,rounds,critical)\u3002\u9700\u6C42\u6587\u6863\uFF1Adoc-set(taskId\uFF1Bwhat/why/trackNote \u81F3\u5C11\u4F20\u4E00)\uFF1Bdoc-acceptance-add(taskId,text)\uFF1Bdoc-non-goal-add(taskId,text)\u3002\u7D20\u6750\uFF1Aarchnote-add(taskId,text)\u3002\u6279\u91CF\u5F62\u6001\uFF08\u63A8\u8350\uFF0C\u516D\u7C7B\u540C\u6784\uFF09\uFF1Arecord-check-add \u4F20 checkItems=[{item,passed?}]\u3001record-artifact-add \u4F20 artifactEntries=[{artifactType,path?/file?/content?,note?}]\u3001record-decision-add \u4F20 decisionEntries=[{topic,decisionText}]\u3001doc-acceptance-add/doc-non-goal-add/archnote-add \u4F20 textItems=[string]\u2014\u2014\u6279\u91CF\u53C2\u6570\u4E0E\u5355\u6761\u53C2\u6570\u4E8C\u9009\u4E00\uFF0C\u4E00\u6B21\u8C03\u7528\u5199\u591A\u6761\uFF08\u4E0A\u9650 50 \u6761/\u6279\uFF0Cartifact \u5168\u6587\u5FEB\u7167\u603B\u91CF \u22642M \u5B57\u7B26/\u6279\uFF09\uFF1B\u90E8\u5206\u5931\u8D25\u4FDD\u7559\u6210\u529F\u6761\u76EE\u5E76\u9010\u6761\u62A5\u544A\uFF08failures[].index=\u539F\u59CB\u63D0\u4EA4\u5E8F\uFF0C\u53EA\u91CD\u8BD5\u5931\u8D25\u9879\uFF09\uFF0Capplied=0 \u6574\u4F53\u62A5\u5931\u8D25\u3002",
1687
2493
  taskArgs,
1688
2494
  async (rawArgs) => {
1689
2495
  const masked = applyFieldMask(rawArgs, TASK_FIELD_SPECS);
@@ -1771,7 +2577,19 @@ function createMcpServer() {
1771
2577
  case "advance": {
1772
2578
  const { taskId } = args;
1773
2579
  if (taskId === void 0) return requireError("advance", ["taskId"], args);
1774
- return mcpContent(await handleAdvance(client, taskId, args.note, args.summary, args.finalize === true));
2580
+ if (args.force === true !== (args.forceReason !== void 0)) {
2581
+ return localError("force \u4E0E forceReason \u5FC5\u987B\u6210\u5BF9\u63D0\u4F9B", '\u5F3A\u5236\u63A8\u8FDB\u4EC5\u51ED\u7528\u6237\u660E\u793A\u6307\u4EE4\uFF1A{"force":true,"forceReason":"<\u7528\u6237\u539F\u8BDD>"}');
2582
+ }
2583
+ const gate = {
2584
+ ...args.force === true ? { force: true } : {},
2585
+ ...args.forceReason !== void 0 ? { forceReason: args.forceReason } : {}
2586
+ };
2587
+ return mcpContent(await handleAdvanceWithGate(client, taskId, gate, args.note, args.summary, args.finalize === true));
2588
+ }
2589
+ case "check": {
2590
+ const { taskId } = args;
2591
+ if (taskId === void 0) return requireError("check", ["taskId"], args);
2592
+ return mcpContent(await handleTaskCheck(client, taskId));
1775
2593
  }
1776
2594
  case "approve": {
1777
2595
  const { taskId, decision } = args;
@@ -1813,11 +2631,26 @@ function createMcpServer() {
1813
2631
  return mcpContent(await handleGetInstance(client, taskId));
1814
2632
  }
1815
2633
  case "record-set": {
1816
- const { taskId, node, summary } = args;
1817
- if (taskId === void 0 || node === void 0 || summary === void 0) {
1818
- return requireError("record-set", ["taskId", "node", "summary"], args);
2634
+ const { taskId, node } = args;
2635
+ if (taskId === void 0 || node === void 0) {
2636
+ return requireError("record-set", ["taskId", "node"], args);
2637
+ }
2638
+ const fields = {
2639
+ ...args.summary !== void 0 ? { summary: args.summary } : {},
2640
+ ...args.baseBranch !== void 0 ? { baseBranch: args.baseBranch } : {},
2641
+ ...args.verifiedCommit !== void 0 ? { verifiedCommit: args.verifiedCommit } : {},
2642
+ ...args.fixesApplied !== void 0 ? { fixesApplied: args.fixesApplied } : {},
2643
+ ...args.testReport !== void 0 ? { testReport: args.testReport } : {},
2644
+ ...args.deferredIssues !== void 0 ? { deferredIssues: args.deferredIssues } : {},
2645
+ ...args.evidenceFiles !== void 0 ? { evidenceFiles: args.evidenceFiles } : {}
2646
+ };
2647
+ if (Object.keys(fields).length === 0) {
2648
+ return localError(
2649
+ "record-set \u9700\u8981\u81F3\u5C11\u4E00\u4E2A\u5B57\u6BB5\uFF08summary \u6216\u7ED3\u6784\u5316\u5B57\u6BB5\uFF09",
2650
+ "\u7ED3\u6784\u5316\u5B57\u6BB5\u6309\u8282\u70B9 prompt\u300C\u7ED3\u6784\u5316\u5B57\u6BB5\u5199\u5165\u8981\u6C42\u300D\u9010\u5B57\u6BB5\u843D\u5E93\uFF1AbaseBranch/verifiedCommit/fixesApplied/testReport/deferredIssues/evidenceFiles"
2651
+ );
1819
2652
  }
1820
- return mcpContent(await handleRecordSummarySet(client, taskId, node, summary));
2653
+ return mcpContent(await handleRecordSummarySet(client, taskId, node, fields));
1821
2654
  }
1822
2655
  case "record-check-add": {
1823
2656
  const { taskId, node } = args;
@@ -1837,7 +2670,13 @@ function createMcpServer() {
1837
2670
  if (taskId === void 0 || node === void 0 || item === void 0) {
1838
2671
  return requireError("record-check-add", ["taskId", "node", "item"], args);
1839
2672
  }
1840
- return mcpContent(await handleRecordCheckAdd(client, taskId, node, item, args.passed ?? true));
2673
+ return mcpContent(
2674
+ await handleRecordCheckAdd(client, taskId, node, item, args.passed ?? true, {
2675
+ // refId=aiChecks 自检留痕锚(审计认 refId===check.id 且 passed);source 本版工具缺省不写
2676
+ ...args.refId !== void 0 ? { refId: args.refId } : {},
2677
+ ...args.source !== void 0 ? { source: args.source } : {}
2678
+ })
2679
+ );
1841
2680
  }
1842
2681
  case "record-check-patch": {
1843
2682
  const { taskId, node, checkId } = args;
@@ -1845,7 +2684,13 @@ function createMcpServer() {
1845
2684
  return requireError("record-check-patch", ["taskId", "node", "checkId"], args);
1846
2685
  }
1847
2686
  return mcpContent(
1848
- await handleRecordCheckPatch(client, taskId, node, checkId, args.passed ?? true)
2687
+ await handleRecordCheckPatch(client, taskId, node, checkId, {
2688
+ // H2:passed 仅显式透传——缺省不默认 true(防未通过留痕被静默翻绿绕过自检门禁)
2689
+ ...args.passed !== void 0 ? { passed: args.passed } : {},
2690
+ ...args.item !== void 0 ? { item: args.item } : {},
2691
+ ...args.refId !== void 0 ? { refId: args.refId } : {},
2692
+ ...args.source !== void 0 ? { source: args.source } : {}
2693
+ })
1849
2694
  );
1850
2695
  }
1851
2696
  case "record-artifact-add": {
@@ -2056,7 +2901,7 @@ function createMcpServer() {
2056
2901
  );
2057
2902
  server.tool(
2058
2903
  "siming_issue",
2059
- `\u95EE\u9898\u6C60\u5DE5\u5177\uFF08T202609150002\uFF09\uFF1A\u5C0F\u95EE\u9898\u4F4E\u6210\u672C\u5165\u6C60\uFF0C\u6C89\u6DC0\u540E\u7EDF\u4E00\u6574\u7406\u8F6C\u4EFB\u52A1\uFF08\u8F6C\u4EFB\u52A1/\u6574\u7406\u91CD\u4EA4\u4E92\u8D70 CLI siming issue convert \u6216 Web\uFF0C\u4E0D\u5728\u672C\u5DE5\u5177\u9762\uFF09\u3002action\uFF1Acreate(title \u5FC5\u586B 1-200 \u5B57\uFF1B\u53EF\u9009 description\u3001projectId\u2014\u2014key|id \u53CC\u5F62\u6001\uFF0C\u901A\u9053\u5C42\u81EA\u52A8\u5F52\u4E00\u4E3A 24-hex id\uFF08hex \u96F6\u7F51\u7EDC\u5F00\u9500\u76F4\u901A\uFF09\uFF1B\u663E\u5F0F\u6307\u5B9A\u89E3\u6790\u5931\u8D25\u62A5\u9519\u4E0D\u515C\u5E95\uFF1B\u7F3A\u7701\u94FE\uFF1A\u672A\u4F20\u6216\u7A7A\u4E32\u65F6\u6309\u5DE5\u4F5C\u533A defaultProject \u89E3\u6790\uFF08\u5931\u6548\u62A5\u9519\uFF09\uFF0C\u65E0\u914D\u7F6E\u65F6\u4E0D\u4F20 projectId \u7531 server \u6309 key=default \u515C\u5E95\uFF1B\u5F55\u5165\u540E source=mcp)\uFF1Bquery(\u53EF\u9009 view=open|all\u2014\u2014\u7F3A\u7701 open=\u6709\u6548\u672A\u8DDF\u8FDB\u5B8C\uFF0Call=\u5168\u91CF\uFF1Bstatus \u8FC7\u6EE4\u2014\u2014\u663E\u5F0F\u6307\u5B9A\u65F6\u4F18\u5148\u4E8E view\uFF0C\u53EF\u67E5\u5DF2\u53D6\u6D88/\u5DF2\u5B8C\u7ED3\uFF1BtaskId=\u4EFB\u52A1\u53CD\u67E5\uFF1BprojectId\u2014\u2014key|id \u53CC\u5F62\u6001\uFF0C\u901A\u9053\u5C42\u81EA\u52A8\u5F52\u4E00\uFF0C\u7A7A/\u672A\u4F20\u4E0D\u8FC7\u6EE4\uFF08\u663E\u5F0F\u89E3\u6790\u5931\u8D25\u62A5\u9519\uFF09\uFF1Bpage\u22651/limit 1-500\uFF1B\u54CD\u5E94 items \u542B progress {done,cancelled,total})\u3002\u8C03\u7528\u65B9\u5E94\u5728 fieldMask \u4E2D\u5217\u51FA\u672C\u6B21\u771F\u5B9E\u63D0\u4F9B\u7684\u53EF\u9009\u5B57\u6BB5\uFF0Cmask \u5916\u7531\u5BBF\u4E3B\u81EA\u52A8\u586B\u5145\u7684\u7A7A\u4E32\u3001\u7A7A\u6570\u7EC4\u3001false\u30010 \u5747\u5FFD\u7565\uFF1B\u5404 action \u56FA\u5B9A\u5FC5\u586B\u5B57\u6BB5\u65E0\u9700\u5199\u5165 mask\u3002`,
2904
+ `\u95EE\u9898\u6C60\u5DE5\u5177\uFF08T202609150002\uFF1BT202609200001 \u5F55\u5165\u5BA1\u6279\u95E8\u69DB\uFF09\uFF1A\u5C0F\u95EE\u9898\u4F4E\u6210\u672C\u5165\u6C60\uFF0C\u6C89\u6DC0\u540E\u7EDF\u4E00\u6574\u7406\u8F6C\u4EFB\u52A1\uFF08\u8F6C\u4EFB\u52A1/\u76F4\u63A5\u6807\u8BB0\u5B8C\u6210\u7B49\u6574\u7406\u91CD\u4EA4\u4E92\u8D70 CLI siming issue convert / siming issue done \u6216 Web\uFF0C\u4E0D\u5728\u672C\u5DE5\u5177\u9762\uFF09\u3002action\uFF1Acreate(title \u5FC5\u586B 1-200 \u5B57\uFF1Bapproved \u5FC5\u586B 1-500 \u5B57\u2014\u2014\u4EBA\u7C7B\u5BA1\u6279\u539F\u8BDD\u9010\u5B57\u5F15\u7528\uFF1A\u5F55\u5165\u524D\u5FC5\u987B\u5148\u5411\u7528\u6237\u5B8C\u6574\u5448\u73B0\u95EE\u9898\uFF08\u73B0\u8C61/\u6839\u56E0/\u5F71\u54CD/\u5EFA\u8BAE\uFF09\u5E76\u83B7\u7528\u6237\u660E\u786E\u6279\u51C6\uFF0C\u503C = \u7528\u6237\u6279\u51C6\u65F6\u8F93\u5165\u7684\u539F\u8BDD\uFF08\u5982\u300COK\uFF0C\u8BB0\u5F55\u300D\uFF09\uFF0C\u7981\u6B62\u4EE3\u586B\u3001\u7F16\u9020\u6216\u4EE5\u8BC4\u4F30\u6027\u8BED\u8A00\u4EE3\u66FF\uFF1B\u4F2A\u9020\u5C5E\u8FDD\u89C4\u5E76\u7559\u5BA1\u8BA1\u75D5\u8FF9\uFF1B\u539F\u8BDD\u843D\u5E93\u4F9B\u4E8B\u540E\u5BA1\u8BA1\uFF08query \u4E0E Web \u8BE6\u60C5\u9875\u53EF\u67E5\uFF09\uFF1B\u53EF\u9009 description\u3001projectId\u2014\u2014key|id \u53CC\u5F62\u6001\uFF0C\u901A\u9053\u5C42\u81EA\u52A8\u5F52\u4E00\u4E3A 24-hex id\uFF08hex \u96F6\u7F51\u7EDC\u5F00\u9500\u76F4\u901A\uFF09\uFF1B\u663E\u5F0F\u6307\u5B9A\u89E3\u6790\u5931\u8D25\u62A5\u9519\u4E0D\u515C\u5E95\uFF1B\u7F3A\u7701\u94FE\uFF1A\u672A\u4F20\u6216\u7A7A\u4E32\u65F6\u6309\u5DE5\u4F5C\u533A defaultProject \u89E3\u6790\uFF08\u5931\u6548\u62A5\u9519\uFF09\uFF0C\u65E0\u914D\u7F6E\u65F6\u4E0D\u4F20 projectId \u7531 server \u6309 key=default \u515C\u5E95\uFF1B\u5F55\u5165\u540E source=mcp\uFF0C\u7F3A approved \u670D\u52A1\u7AEF 400 \u5E76\u8FD4\u56DE\u81EA\u89E3\u91CA\u6587\u6848)\uFF1Bquery(\u53EF\u9009 view=open|all\u2014\u2014\u7F3A\u7701 open=\u6709\u6548\u672A\u8DDF\u8FDB\u5B8C\uFF0Call=\u5168\u91CF\uFF1Bstatus \u8FC7\u6EE4\u2014\u2014pending|partial|converted|cancelled|done\uFF0C\u663E\u5F0F\u6307\u5B9A\u65F6\u4F18\u5148\u4E8E view\uFF0C\u53EF\u67E5\u5DF2\u53D6\u6D88/\u5DF2\u5B8C\u6210\uFF1BtaskId=\u4EFB\u52A1\u53CD\u67E5\uFF1BprojectId\u2014\u2014key|id \u53CC\u5F62\u6001\uFF0C\u901A\u9053\u5C42\u81EA\u52A8\u5F52\u4E00\uFF0C\u7A7A/\u672A\u4F20\u4E0D\u8FC7\u6EE4\uFF08\u663E\u5F0F\u89E3\u6790\u5931\u8D25\u62A5\u9519\uFF09\uFF1Bpage\u22651/limit 1-500\uFF1B\u54CD\u5E94 items \u542B progress {done,cancelled,total})\u3002\u8C03\u7528\u65B9\u5E94\u5728 fieldMask \u4E2D\u5217\u51FA\u672C\u6B21\u771F\u5B9E\u63D0\u4F9B\u7684\u53EF\u9009\u5B57\u6BB5\uFF0Cmask \u5916\u7531\u5BBF\u4E3B\u81EA\u52A8\u586B\u5145\u7684\u7A7A\u4E32\u3001\u7A7A\u6570\u7EC4\u3001false\u30010 \u5747\u5FFD\u7565\uFF1B\u5404 action \u56FA\u5B9A\u5FC5\u586B\u5B57\u6BB5\u65E0\u9700\u5199\u5165 mask\u3002`,
2060
2905
  issueArgs,
2061
2906
  async (rawArgs) => {
2062
2907
  const masked = applyFieldMask(rawArgs, ISSUE_FIELD_SPECS);
@@ -2064,9 +2909,9 @@ function createMcpServer() {
2064
2909
  const args = masked.args;
2065
2910
  switch (args.action) {
2066
2911
  case "create": {
2067
- const { title } = args;
2068
- if (title === void 0) {
2069
- return requireError("create", ["title"], args);
2912
+ const { title, approved } = args;
2913
+ if (title === void 0 || approved === void 0) {
2914
+ return requireError("create", ["title", "approved"], args);
2070
2915
  }
2071
2916
  const projectR = await resolveScopedProject(client, args.projectId, "ws-default", { channel: "mcp" });
2072
2917
  if (!projectR.success) return mcpContent(projectR);
@@ -2075,7 +2920,8 @@ function createMcpServer() {
2075
2920
  await handleCreateIssue(client, {
2076
2921
  title,
2077
2922
  ...args.description !== void 0 ? { description: args.description } : {},
2078
- ...projectId !== void 0 ? { projectId } : {}
2923
+ ...projectId !== void 0 ? { projectId } : {},
2924
+ approved
2079
2925
  })
2080
2926
  );
2081
2927
  }
@@ -2432,9 +3278,29 @@ function createTaskCommand() {
2432
3278
  }
2433
3279
  renderResult("json", result);
2434
3280
  });
2435
- task.command("advance <taskId>").description("Advance a task to the next node (record contents already written via `task record/doc`; this is the light finish). At the final node the advance is rejected unless --finalize is passed (completion confirmation)").option("--url <url>", "server base URL").option("--format <fmt>", "output format", "json").option("--note <text>", "advance note recorded in history").option("--summary <text>", "finishing summary written to nodeRecords.<node>.summary (equivalent to a final `record set --summary`)").option("--finalize", "completion confirmation: finalize the task at its final node (required there; ignored mid-flow)").action(async (taskId, opts) => {
3281
+ task.command("advance <taskId>").description("Advance a task to the next node (record contents already written via `task record/doc`; this is the light finish). At the final node the advance is rejected unless --finalize is passed (completion confirmation). Node gate checks run before the request; failures block the advance").option("--url <url>", "server base URL").option("--format <fmt>", "output format", "json").option("--note <text>", "advance note recorded in history").option("--summary <text>", "finishing summary written to nodeRecords.<node>.summary (equivalent to a final `record set --summary`)").option("--finalize", "completion confirmation: finalize the task at its final node (required there; ignored mid-flow)").option("--force", "bypass gate failures (user instruction; requires --force-reason, audit trail written to history)").option("--force-reason <text>", "verbatim user instruction justifying --force (audit trail)").action(async (taskId, opts) => {
3282
+ if (opts.force === true !== (opts.forceReason !== void 0)) {
3283
+ renderResult(opts.format, fail({
3284
+ message: "--force \u4E0E --force-reason \u5FC5\u987B\u6210\u5BF9\u63D0\u4F9B",
3285
+ hint: '\u7528\u6CD5\uFF1Asiming task advance <taskId> --force --force-reason "<\u7528\u6237\u660E\u793A\u539F\u6587>"\uFF1B\u7559\u75D5\u5C06\u5199\u5165\u4EFB\u52A1 history',
3286
+ httpStatus: 0
3287
+ }));
3288
+ return;
3289
+ }
3290
+ const client = createClient(opts.url);
3291
+ const result = opts.force === true ? await handleAdvanceWithGate(
3292
+ client,
3293
+ taskId,
3294
+ opts.forceReason === void 0 ? { force: true } : { force: true, forceReason: opts.forceReason },
3295
+ opts.note,
3296
+ opts.summary,
3297
+ opts.finalize === true
3298
+ ) : await handleAdvance(client, taskId, opts.note, opts.summary, opts.finalize === true);
3299
+ renderResult(opts.format, result);
3300
+ });
3301
+ task.command("check <taskId>").description("Read-only gate check: run current node tool/ai checks and print the result matrix (no writes, no history, no advance)").option("--url <url>", "server base URL").option("--format <fmt>", "output format", "json").action(async (taskId, opts) => {
2436
3302
  const client = createClient(opts.url);
2437
- const result = await handleAdvance(client, taskId, opts.note, opts.summary, opts.finalize === true);
3303
+ const result = await handleTaskCheck(client, taskId);
2438
3304
  renderResult(opts.format, result);
2439
3305
  });
2440
3306
  task.command("approve <taskId>").description("Approve or reject a task paused at a human_approval pause point").option("--url <url>", "server base URL").option("--format <fmt>", "output format", "json").requiredOption("--decision <d>", "decision: approved|rejected").option("--comment <text>", "decision comment recorded in history").action(async (taskId, opts) => {
@@ -2569,17 +3435,60 @@ function createTaskCommand() {
2569
3435
  renderResult(opts.format, result, { fields: result.success ? nodeFields(result.data) : [] });
2570
3436
  });
2571
3437
  const record = task.command("record").description("Node execution records \u2014 summary/checks/artifacts/confirmations/decisions/review (draft-writable, small-step append)");
2572
- withCommonOpts(record.command("set <taskId>"), "fancy").description("Set node summary (draft during execution; advance --summary is the finishing equivalent)").requiredOption("--node <nid>", "node id (see `task context`)").requiredOption("--summary <text>", "node summary text").action(async (taskId, opts) => {
2573
- const client = createClient(opts.url);
2574
- const result = await handleRecordSummarySet(client, taskId, opts.node, opts.summary);
2575
- if (result.success) {
2576
- renderResult(opts.format, result, { successMessage: `${taskId} \u8282\u70B9 ${opts.node} summary \u5DF2\u5199\u5165` });
2577
- } else {
2578
- renderResult(opts.format, result);
3438
+ withCommonOpts(record.command("set <taskId>"), "fancy").description("Set node record fields \u2014 --summary and/or structured fields (T202609230001: baseBranch/testReport/... ; at least one required)").requiredOption("--node <nid>", "node id (see `task context`)").option("--summary <text>", "node summary text").option("--base-branch <branch>", "align \u57FA\u7840\u5206\u652F\uFF08test-integrate \u7B49\u8282\u70B9\u7ED3\u6784\u5316\u5199\u5165\uFF09").option("--verified-commit <sha>", "\u9A8C\u8BC1\u901A\u8FC7\u5BF9\u5E94\u7684\u63D0\u4EA4\u53F7\uFF08verifiedCommit\uFF09").option("--fixes-applied <n>", "fixesApplied \u4FEE\u590D\u6B21\u6570\uFF08\u975E\u8D1F\u6574\u6570\uFF1BB4 \u6761\u4EF6\u95E8\u7981\uFF1A>0 \u987B\u8BC4\u5BA1 verdict=pass\uFF09").option("--test-report <json>", `testReport JSON\uFF1A'{"command":"pnpm test","passed":10,"failed":0,"skipped":0}'`).option("--deferred-issues <list>", "\u5EF6\u540E\u95EE\u9898 id \u5217\u8868\uFF08\u9017\u53F7\u5206\u9694\uFF09").option("--evidence-files <list>", "\u8BC1\u636E\u6587\u4EF6\u8DEF\u5F84\u5217\u8868\uFF08\u9017\u53F7\u5206\u9694\uFF09").action(
3439
+ async (taskId, opts) => {
3440
+ const client = createClient(opts.url);
3441
+ let fixesApplied;
3442
+ let testReport;
3443
+ if (opts.fixesApplied !== void 0) {
3444
+ const n = Number(opts.fixesApplied);
3445
+ if (!Number.isInteger(n) || n < 0) {
3446
+ renderResult(opts.format, fail({ message: `--fixes-applied \u987B\u4E3A\u975E\u8D1F\u6574\u6570\uFF08\u6536\u5230 ${opts.fixesApplied}\uFF09`, httpStatus: 0 }));
3447
+ return;
3448
+ }
3449
+ fixesApplied = n;
3450
+ }
3451
+ if (opts.testReport !== void 0) {
3452
+ try {
3453
+ testReport = JSON.parse(opts.testReport);
3454
+ } catch {
3455
+ renderResult(opts.format, fail({ message: `--test-report JSON \u89E3\u6790\u5931\u8D25: ${opts.testReport}`, httpStatus: 0 }));
3456
+ return;
3457
+ }
3458
+ }
3459
+ const splitList = (raw) => raw === void 0 ? void 0 : raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
3460
+ const deferredIssues = splitList(opts.deferredIssues);
3461
+ const evidenceFiles = splitList(opts.evidenceFiles);
3462
+ const fields = {
3463
+ ...opts.summary !== void 0 ? { summary: opts.summary } : {},
3464
+ ...opts.baseBranch !== void 0 ? { baseBranch: opts.baseBranch } : {},
3465
+ ...opts.verifiedCommit !== void 0 ? { verifiedCommit: opts.verifiedCommit } : {},
3466
+ ...fixesApplied !== void 0 ? { fixesApplied } : {},
3467
+ ...testReport !== void 0 ? { testReport } : {},
3468
+ ...deferredIssues !== void 0 ? { deferredIssues } : {},
3469
+ ...evidenceFiles !== void 0 ? { evidenceFiles } : {}
3470
+ };
3471
+ if (Object.keys(fields).length === 0) {
3472
+ renderResult(
3473
+ opts.format,
3474
+ fail({
3475
+ message: "\u9700\u8981\u81F3\u5C11\u4E00\u4E2A\u5B57\u6BB5\uFF1A--summary \u6216\u7ED3\u6784\u5316\u5B57\u6BB5\uFF08--base-branch/--verified-commit/--fixes-applied/--test-report/--deferred-issues/--evidence-files\uFF09",
3476
+ httpStatus: 0,
3477
+ hint: "\u7ED3\u6784\u5316\u5B57\u6BB5\u6309\u8282\u70B9 prompt\u300C\u7ED3\u6784\u5316\u5B57\u6BB5\u5199\u5165\u8981\u6C42\u300D\u9010\u5B57\u6BB5\u843D\u5E93\uFF08summary PATCH \u8BED\u4E49=\u4EC5\u66F4\u65B0\u63D0\u4F9B\u5B57\u6BB5\uFF09"
3478
+ })
3479
+ );
3480
+ return;
3481
+ }
3482
+ const result = await handleRecordSummarySet(client, taskId, opts.node, fields);
3483
+ if (result.success) {
3484
+ renderResult(opts.format, result, { successMessage: `${taskId} \u8282\u70B9 ${opts.node} record \u5DF2\u5199\u5165\uFF08${Object.keys(fields).join("/")}\uFF09` });
3485
+ } else {
3486
+ renderResult(opts.format, result);
3487
+ }
2579
3488
  }
2580
- });
3489
+ );
2581
3490
  const check = record.command("check").description("Completion-check items (server-generated short ids)");
2582
- withCommonOpts(check.command("add <taskId>"), "fancy").description("Append completion-check item(s) \u2014 single --item (legacy form) / repeat --item or --entry JSON for batch (T202609010002)").requiredOption("--node <nid>", "node id").option("--item <text>", "check item text; repeatable \xD72+ = batch (single occurrence without --entry = single form; mixed with --entry, --item group comes first in submit order)", collectArgs, []).option("--entry <json>", 'batch element {"item":"...","passed":true} (repeatable; passed per-item, default true)', collectArgs, []).option("--failed", "record as not passed (single form only; mutually exclusive with batch params)").action(async (taskId, opts) => {
3491
+ withCommonOpts(check.command("add <taskId>"), "fancy").description("Append completion-check item(s) \u2014 single --item (legacy form) / repeat --item or --entry JSON for batch (T202609010002)").requiredOption("--node <nid>", "node id").option("--item <text>", "check item text; repeatable \xD72+ = batch (single occurrence without --entry = single form; mixed with --entry, --item group comes first in submit order)", collectArgs, []).option("--entry <json>", 'batch element {"item":"...","passed":true,"refId":"ai-check-id","source":"tool"} (repeatable; passed per-item default true; refId=aiChecks \u81EA\u68C0\u7559\u75D5\u951A)', collectArgs, []).option("--failed", "record as not passed (single form only; mutually exclusive with batch params)").option("--ref-id <id>", "aiCheck id \u7559\u75D5\u951A\uFF08\u5355\u6761\u5F62\u6001\uFF1B\u5BA1\u8BA1\u8BA4 refId===aiCheck.id \u4E14 passed\uFF09").option("--source <src>", "\u6761\u76EE\u6765\u6E90\u6807\u8BB0\uFF08self|tool\uFF1B\u672C\u7248\u5DE5\u5177\u7F3A\u7701\u4E0D\u5199\uFF0Cself \u4E3A AI \u81EA\u62A5\u7F3A\u7701\uFF09").action(async (taskId, opts) => {
2583
3492
  const client = createClient(opts.url);
2584
3493
  const items = opts.item ?? [];
2585
3494
  const rawEntries = opts.entry ?? [];
@@ -2601,7 +3510,14 @@ function createTaskCommand() {
2601
3510
  return;
2602
3511
  }
2603
3512
  if (!batchMode) {
2604
- const result2 = await handleRecordCheckAdd(client, taskId, opts.node, items[0], !opts.failed);
3513
+ if (opts.source !== void 0 && opts.source !== "self" && opts.source !== "tool") {
3514
+ renderResult(opts.format, fail({ message: `--source \u4EC5\u652F\u6301 self|tool\uFF08\u6536\u5230 ${opts.source}\uFF09`, httpStatus: 0 }));
3515
+ return;
3516
+ }
3517
+ const result2 = await handleRecordCheckAdd(client, taskId, opts.node, items[0], !opts.failed, {
3518
+ ...opts.refId !== void 0 ? { refId: opts.refId } : {},
3519
+ ...opts.source !== void 0 ? { source: opts.source } : {}
3520
+ });
2605
3521
  if (result2.success) {
2606
3522
  renderResult(opts.format, result2, { successMessage: `${taskId} \u8282\u70B9 ${opts.node} check \u5DF2\u6DFB\u52A0\uFF08\u65B0\u6761\u76EE id ${result2.data.entry?.id ?? "?"}\uFF09` });
2607
3523
  } else {
@@ -2625,12 +3541,25 @@ function createTaskCommand() {
2625
3541
  localFailures.push({ index: submitIndex, message: "passed \u987B\u4E3A\u5E03\u5C14" });
2626
3542
  return;
2627
3543
  }
3544
+ if (j.refId !== void 0 && typeof j.refId !== "string") {
3545
+ localFailures.push({ index: submitIndex, message: "refId \u987B\u4E3A\u5B57\u7B26\u4E32\uFF08aiCheck id\uFF09" });
3546
+ return;
3547
+ }
3548
+ if (j.source !== void 0 && j.source !== "self" && j.source !== "tool") {
3549
+ localFailures.push({ index: submitIndex, message: "source \u4EC5\u652F\u6301 self|tool" });
3550
+ return;
3551
+ }
2628
3552
  entryPairs.push({
2629
- entry: { item: j.item, ...typeof j.passed === "boolean" ? { passed: j.passed } : {} },
3553
+ entry: {
3554
+ item: j.item,
3555
+ ...typeof j.passed === "boolean" ? { passed: j.passed } : {},
3556
+ ...typeof j.refId === "string" ? { refId: j.refId } : {},
3557
+ ...j.source === "self" || j.source === "tool" ? { source: j.source } : {}
3558
+ },
2630
3559
  rawIndex: i
2631
3560
  });
2632
3561
  } catch {
2633
- localFailures.push({ index: submitIndex, message: `--entry JSON \u89E3\u6790\u5931\u8D25: ${raw}`, hint: '\u5143\u7D20\u5F62\u5982 {"item":"...","passed":true}' });
3562
+ localFailures.push({ index: submitIndex, message: `--entry JSON \u89E3\u6790\u5931\u8D25: ${raw}`, hint: '\u5143\u7D20\u5F62\u5982 {"item":"...","passed":true,"refId":"..."}' });
2634
3563
  }
2635
3564
  });
2636
3565
  const submitted = [...itemElements, ...entryPairs.map((p) => p.entry)];
@@ -2647,7 +3576,7 @@ function createTaskCommand() {
2647
3576
  });
2648
3577
  withCommonOpts(check.command("patch <taskId>"), "fancy").description("Flip one check item pass/fail state by check id").requiredOption("--node <nid>", "node id").requiredOption("--check <cid>", "check entry id (from check add output / task context)").option("--failed", "flip to not passed (default: passed)").action(async (taskId, opts) => {
2649
3578
  const client = createClient(opts.url);
2650
- const result = await handleRecordCheckPatch(client, taskId, opts.node, opts.check, !opts.failed);
3579
+ const result = await handleRecordCheckPatch(client, taskId, opts.node, opts.check, { passed: !opts.failed });
2651
3580
  if (result.success) {
2652
3581
  renderResult(opts.format, result, { successMessage: `${taskId} \u8282\u70B9 ${opts.node} check ${opts.check} \u2192 ${opts.failed ? "failed" : "passed"}` });
2653
3582
  } else {
@@ -2967,7 +3896,7 @@ import { Command as Command3 } from "commander";
2967
3896
  import { SKILL_MAIN_DOC, SkillScopeSchema } from "@siming-org/core";
2968
3897
 
2969
3898
  // src/client/scaffold-templates.ts
2970
- import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync11 } from "fs";
3899
+ import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync11 } from "fs";
2971
3900
  import { dirname as dirname3, join as join6 } from "path";
2972
3901
  import { fileURLToPath } from "url";
2973
3902
  var SCAFFOLD_DIR = join6("templates", "project-skills");
@@ -2976,7 +3905,7 @@ function resolveCliPackageRoot() {
2976
3905
  let dir = dirname3(fileURLToPath(import.meta.url));
2977
3906
  for (let depth = 0; depth < 10; depth += 1) {
2978
3907
  const pkgFile = join6(dir, "package.json");
2979
- if (existsSync6(pkgFile)) {
3908
+ if (existsSync7(pkgFile)) {
2980
3909
  try {
2981
3910
  const pkg = JSON.parse(readFileSync11(pkgFile, "utf-8"));
2982
3911
  if (pkg.name === "@siming-org/cli") return ok2(dir);
@@ -4289,6 +5218,7 @@ import { Command as Command6 } from "commander";
4289
5218
  import { SkillScopeSchema as SkillScopeSchema2 } from "@siming-org/core";
4290
5219
 
4291
5220
  // src/client/handlers/node.ts
5221
+ import { assertUniqueAiCheckIds } from "@siming-org/core";
4292
5222
  async function handleListNodePresets(client, query = {}) {
4293
5223
  const qs = new URLSearchParams();
4294
5224
  if (query.projectId) qs.set("projectId", query.projectId);
@@ -4420,6 +5350,8 @@ ${failures.join("\n")}`,
4420
5350
  prompt: preset.prompt,
4421
5351
  skills: [...preset.skills],
4422
5352
  agents: [...preset.agents ?? []],
5353
+ ...preset.toolChecks !== void 0 ? { toolChecks: preset.toolChecks.map((c) => c.params ? { ...c, params: { ...c.params } } : { ...c }) } : {},
5354
+ ...preset.aiChecks !== void 0 ? { aiChecks: preset.aiChecks.map((c) => ({ ...c })) } : {},
4423
5355
  sourcePreset: {
4424
5356
  code: preset.code,
4425
5357
  version: preset.version,
@@ -4427,6 +5359,18 @@ ${failures.join("\n")}`,
4427
5359
  agents: [...preset.agents ?? []]
4428
5360
  }
4429
5361
  }));
5362
+ try {
5363
+ assertUniqueAiCheckIds(nodes);
5364
+ } catch (err2) {
5365
+ return {
5366
+ success: false,
5367
+ error: {
5368
+ message: `\u7EC4\u5408 "${compositionName}" aiCheck id \u51B2\u7A81\uFF1A${err2 instanceof Error ? err2.message : String(err2)}`,
5369
+ httpStatus: 0,
5370
+ hint: "\u4FEE\u6B63\u8282\u70B9\u9884\u8BBE\u7684 aiChecks id \u6216\u7EC4\u5408\u5185\u6539 nodeId \u540E\u91CD\u8BD5"
5371
+ }
5372
+ };
5373
+ }
4430
5374
  const nodeIds = new Set(nodes.map((n) => n.id));
4431
5375
  const danglingEdges = composition.edges.filter((e) => !nodeIds.has(e.from) || !nodeIds.has(e.to)).map((e) => `${e.from}\u2192${e.to}`);
4432
5376
  if (danglingEdges.length > 0) {
@@ -4547,7 +5491,7 @@ function createNodeCommand() {
4547
5491
  };
4548
5492
  renderResult("json", await handleCreateNodePreset(client, input));
4549
5493
  });
4550
- node.command("update <code>").description("Update a node preset (partial; content change requires version bump \u2014 422 NODE_PRESET_VERSION_REQUIRED)").option("--url <url>", "server base URL").option("--label <text>", "new label").option("--node-id <id>", "new default node id").option("--phase <phase>", "new phase").option("--track <track>", "new track").option("--prompt <text>", "new prompt body").option("--prompt-file <path>", "read prompt from file (overrides --prompt)").option("--skill <name>", "replace bound skills list (repeatable; omit = untouched)", collectArgs).option("--clear-skills", "clear bound skills list").option("--agent <name>", "replace callable agents list (repeatable; omit = untouched)", collectArgs).option("--clear-agents", "clear callable agents list").option("--description <text>", "new description").option("--new-version <v>", "new version (required when content fields change)").action(async (code, opts) => {
5494
+ node.command("update <code>").description("Update a node preset (partial; content change requires version bump \u2014 422 NODE_PRESET_VERSION_REQUIRED)").option("--url <url>", "server base URL").option("--label <text>", "new label").option("--node-id <id>", "new default node id").option("--phase <phase>", "new phase").option("--track <track>", "new track").option("--prompt <text>", "new prompt body").option("--prompt-file <path>", "read prompt from file (overrides --prompt)").option("--skill <name>", "replace bound skills list (repeatable; omit = untouched)", collectArgs).option("--clear-skills", "clear bound skills list").option("--agent <name>", "replace callable agents list (repeatable; omit = untouched)", collectArgs).option("--clear-agents", "clear callable agents list").option("--description <text>", "new description").option("--checks-file <path>", 'checks \u76F4\u66F4\u901A\u9053\uFF08T202609230001 \u8BBE\u8BA1 :300\uFF09\uFF1AJSON \u6587\u4EF6 {"toolChecks":[],"aiChecks":[]} \u6574\u4F53\u66FF\u6362 checks \u9762\uFF08\u7F3A\u952E=\u6E05\u7A7A\uFF09\uFF1B\u5185\u5BB9\u53D8\u66F4\u540C\u6279 --new-version').option("--new-version <v>", "new version (required when content fields change)").action(async (code, opts) => {
4551
5495
  const client = createClient(opts.url);
4552
5496
  let prompt;
4553
5497
  if (opts.promptFile !== void 0) {
@@ -4563,6 +5507,30 @@ function createNodeCommand() {
4563
5507
  } else if (opts.prompt !== void 0) {
4564
5508
  prompt = opts.prompt;
4565
5509
  }
5510
+ let checks;
5511
+ if (opts.checksFile !== void 0) {
5512
+ let parsed;
5513
+ try {
5514
+ parsed = JSON.parse(readFileSync13(opts.checksFile, "utf-8"));
5515
+ } catch (e) {
5516
+ renderResult("json", fail({
5517
+ message: `checks \u6587\u4EF6\u8BFB\u53D6/\u89E3\u6790\u5931\u8D25: ${opts.checksFile}\uFF08${e instanceof Error ? e.message : String(e)}\uFF09`,
5518
+ httpStatus: 0
5519
+ }));
5520
+ return;
5521
+ }
5522
+ const obj = parsed;
5523
+ const badTool = obj.toolChecks !== void 0 && (!Array.isArray(obj.toolChecks) || obj.toolChecks.some((t) => typeof t?.type !== "string"));
5524
+ const badAi = obj.aiChecks !== void 0 && (!Array.isArray(obj.aiChecks) || obj.aiChecks.some((a) => typeof a?.id !== "string" || typeof a?.item !== "string"));
5525
+ if (badTool || badAi) {
5526
+ renderResult("json", fail({
5527
+ message: '--checks-file \u987B\u4E3A {"toolChecks":[{"type":"..."}],"aiChecks":[{"id":"...","item":"..."}]}\uFF08\u7F3A\u952E=\u6E05\u7A7A\u8BE5\u6570\u7EC4\uFF09',
5528
+ httpStatus: 0
5529
+ }));
5530
+ return;
5531
+ }
5532
+ checks = { toolChecks: obj.toolChecks ?? [], aiChecks: obj.aiChecks ?? [] };
5533
+ }
4566
5534
  if (opts.skill !== void 0 && opts.clearSkills) {
4567
5535
  renderResult("json", fail({ message: "--skill \u4E0E --clear-skills \u4E92\u65A5", httpStatus: 0 }));
4568
5536
  return;
@@ -4580,10 +5548,11 @@ function createNodeCommand() {
4580
5548
  ...opts.skill !== void 0 ? { skills: opts.skill } : opts.clearSkills ? { skills: [] } : {},
4581
5549
  ...opts.agent !== void 0 ? { agents: opts.agent } : opts.clearAgents ? { agents: [] } : {},
4582
5550
  ...opts.description !== void 0 ? { description: opts.description } : {},
5551
+ ...checks !== void 0 ? { toolChecks: checks.toolChecks, aiChecks: checks.aiChecks } : {},
4583
5552
  ...opts.newVersion !== void 0 ? { version: opts.newVersion } : {}
4584
5553
  };
4585
5554
  if (Object.keys(patch).length === 0) {
4586
- renderResult("json", fail({ message: "\u672A\u643A\u5E26\u4EFB\u4F55\u5F85\u66F4\u65B0\u5B57\u6BB5", httpStatus: 0, hint: "\u53EF\u66F4\u65B0\uFF1Alabel/node-id/phase/track/prompt/skill/clear-skills/agent/clear-agents/description/new-version" }));
5555
+ renderResult("json", fail({ message: "\u672A\u643A\u5E26\u4EFB\u4F55\u5F85\u66F4\u65B0\u5B57\u6BB5", httpStatus: 0, hint: "\u53EF\u66F4\u65B0\uFF1Alabel/node-id/phase/track/prompt/skill/clear-skills/agent/clear-agents/description/checks-file/new-version" }));
4587
5556
  return;
4588
5557
  }
4589
5558
  renderResult("json", await handleUpdateNodePreset(client, code, patch));
@@ -4647,11 +5616,11 @@ function nodeFields2(n) {
4647
5616
  }
4648
5617
 
4649
5618
  // src/commands/flow/command.ts
4650
- import { execFile } from "child_process";
5619
+ import { execFile as execFile2 } from "child_process";
4651
5620
  import { mkdtempSync, rmSync as rmSync2 } from "fs";
4652
5621
  import { tmpdir } from "os";
4653
5622
  import { join as join7 } from "path";
4654
- import { promisify } from "util";
5623
+ import { promisify as promisify2 } from "util";
4655
5624
  import { createInterface } from "readline/promises";
4656
5625
  import { Command as Command7 } from "commander";
4657
5626
  import {
@@ -4668,7 +5637,7 @@ import {
4668
5637
  SKILL_MAIN_DOC as SKILL_MAIN_DOC2,
4669
5638
  createReferencesSchema as createReferencesSchema2
4670
5639
  } from "@siming-org/core";
4671
- var FLOW_MANIFEST_FORMAT_VERSION = "1.1";
5640
+ var FLOW_MANIFEST_FORMAT_VERSION = "1.2";
4672
5641
  var uniqueStrings = (values, ctx, label) => {
4673
5642
  const seen = /* @__PURE__ */ new Set();
4674
5643
  for (let index = 0; index < values.length; index += 1) {
@@ -4687,6 +5656,14 @@ var manifestCapabilityRefSchema = z6.object({
4687
5656
  var capabilityRefsSchema = z6.array(manifestCapabilityRefSchema).superRefine((refs, ctx) => {
4688
5657
  uniqueStrings(refs.map((ref) => ref.name), ctx, "\u80FD\u529B\u540D\u79F0");
4689
5658
  });
5659
+ var manifestToolCheckSchema = z6.object({
5660
+ type: z6.string().min(1).max(50).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "kebab-case only"),
5661
+ params: z6.record(z6.string(), z6.union([z6.string(), z6.number(), z6.boolean()])).optional()
5662
+ });
5663
+ var manifestAiCheckSchema = z6.object({
5664
+ id: z6.string().min(2).max(30).regex(/^[a-z0-9-]+$/),
5665
+ item: z6.string().min(1).max(200)
5666
+ });
4690
5667
  var nodePresetPayloadBaseSchema = z6.object({
4691
5668
  code: z6.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "kebab-case only"),
4692
5669
  label: z6.string().min(1),
@@ -4696,7 +5673,9 @@ var nodePresetPayloadBaseSchema = z6.object({
4696
5673
  prompt: z6.string().min(1),
4697
5674
  skills: capabilityRefsSchema,
4698
5675
  description: z6.string(),
4699
- version: z6.string().min(1)
5676
+ version: z6.string().min(1),
5677
+ toolChecks: z6.array(manifestToolCheckSchema).max(20).optional(),
5678
+ aiChecks: z6.array(manifestAiCheckSchema).max(20).optional()
4700
5679
  }).strict();
4701
5680
  var nodePresetPayloadSchema = nodePresetPayloadBaseSchema.extend({ agents: capabilityRefsSchema }).strict();
4702
5681
  var referenceDocsSchema = (mainDoc) => createReferencesSchema2(mainDoc);
@@ -4970,7 +5949,7 @@ function compareSemverStrings(a, b) {
4970
5949
  }
4971
5950
 
4972
5951
  // src/commands/flow/command.ts
4973
- var execFileAsync = promisify(execFile);
5952
+ var execFileAsync2 = promisify2(execFile2);
4974
5953
  function createFlowCommand() {
4975
5954
  const flow = new Command7("flow").description("Node library management \u2014 install npm-distributed node packages & compose templates");
4976
5955
  withCommonOpts(flow.command("list"), "table").description("List installed node libraries (registry entries written by `flow install`)").action(async (opts) => {
@@ -5096,7 +6075,7 @@ async function fetchManifest(pkgSpec, registry) {
5096
6075
  }
5097
6076
  };
5098
6077
  try {
5099
- const { stdout } = await execFileAsync(
6078
+ const { stdout } = await execFileAsync2(
5100
6079
  "npm",
5101
6080
  [
5102
6081
  "pack",
@@ -5117,7 +6096,7 @@ async function fetchManifest(pkgSpec, registry) {
5117
6096
  hint: "\u786E\u8BA4 npm \u7248\u672C\u652F\u6301 --json \u8F93\u51FA"
5118
6097
  });
5119
6098
  }
5120
- const { stdout: manifestText } = await execFileAsync(
6099
+ const { stdout: manifestText } = await execFileAsync2(
5121
6100
  "tar",
5122
6101
  ["-xOf", join7(dir, filename), "package/siming-nodes.json"],
5123
6102
  { maxBuffer: MANIFEST_LIMITS.maxChars * 2 + 64 * 1024 }
@@ -5527,6 +6506,8 @@ function diffNode(payload, entity, packageName) {
5527
6506
  if (entity.version !== payload.version) diffs.push("version");
5528
6507
  if ((entity.description ?? "") !== payload.description) diffs.push("description");
5529
6508
  if (entity.source?.package !== packageName) diffs.push("source");
6509
+ if (JSON.stringify(entity.toolChecks ?? []) !== JSON.stringify(payload.toolChecks ?? [])) diffs.push("toolChecks");
6510
+ if (JSON.stringify(entity.aiChecks ?? []) !== JSON.stringify(payload.aiChecks ?? [])) diffs.push("aiChecks");
5530
6511
  return diffs;
5531
6512
  }
5532
6513
  function sameOrdered(left, right) {
@@ -5578,6 +6559,9 @@ function snapshotNode(entity) {
5578
6559
  prompt: entity.prompt,
5579
6560
  skills: [...entity.skills].sort(),
5580
6561
  agents: [...entity.agents ?? []].sort(),
6562
+ // T202609230001:门禁声明入快照(plan→apply 窗口期门禁被改应触发 FLOW_INSTALL_STALE 而非静默覆盖)
6563
+ toolChecks: entity.toolChecks ?? [],
6564
+ aiChecks: entity.aiChecks ?? [],
5581
6565
  scope: entity.scope,
5582
6566
  projectId: entity.projectId ?? null,
5583
6567
  description: entity.description ?? null,
@@ -5807,6 +6791,9 @@ async function writeAsset(client, manifest, plan, item) {
5807
6791
  description: payload.description,
5808
6792
  version: payload.version,
5809
6793
  source: { package: manifest.name },
6794
+ // T202609230001:门禁声明随安装写入(缺此两行 = 安装成功但门禁静默丢失)
6795
+ ...payload.toolChecks !== void 0 ? { toolChecks: payload.toolChecks } : {},
6796
+ ...payload.aiChecks !== void 0 ? { aiChecks: payload.aiChecks } : {},
5810
6797
  ...item.targetProjectId !== void 0 ? { projectId: item.targetProjectId } : {}
5811
6798
  };
5812
6799
  return handleCreateNodePreset(client, input);
@@ -5821,7 +6808,12 @@ async function writeAsset(client, manifest, plan, item) {
5821
6808
  agents: payload.agents.map((ref) => ref.name),
5822
6809
  description: payload.description,
5823
6810
  version: payload.version,
5824
- source: { package: manifest.name }
6811
+ source: { package: manifest.name },
6812
+ // T202609230001:门禁声明随升级写入。显式归一空数组——「包未声明=清除」必须以 [] 落到线上
6813
+ // (裸 undefined 经 JSON.stringify 丢键,server 保留旧值 → 写后回读不一致 → FLOW_INSTALL_STALE;
6814
+ // 与 diffNode/模板升级的「缺省=空」归一语义一致)
6815
+ toolChecks: payload.toolChecks ?? [],
6816
+ aiChecks: payload.aiChecks ?? []
5825
6817
  };
5826
6818
  return handleUpdateNodePreset(client, payload.code, patch);
5827
6819
  }
@@ -5977,11 +6969,57 @@ function createProjectCommand() {
5977
6969
  const result = await handleGetProject(client, id);
5978
6970
  renderResult(opts.format, result, { fields: result.success ? projectFields(result.data) : [] });
5979
6971
  });
5980
- project.command("update <id>").description("Update a project (name/description)").option("--url <url>", "server base URL").option("--name <name>", "new name").option("--description <text>", "new description").action(async (id, opts) => {
6972
+ project.command("update <id>").description("Update a project (name/description/status/vcs gate config). vcs is replaced as a whole: any --vcs-* flag replaces the entire vcs object with only the provided fields").option("--url <url>", "server base URL").option("--name <name>", "new name").option("--description <text>", "new description").option("--vcs-enabled <bool>", "gate VCS checks for this project (true/false; false=all git checks N/A)").option("--vcs-repo-path <path>", "repository absolute path (default: MCP host cwd probing)").option("--vcs-base-branch <branch>", "base branch (2nd level of the base-branch resolution chain)").option("--vcs-branch-pattern <pattern>", "task branch prefix pattern for A1").option("--vcs-worktree-mode <bool>", "worktree parallel mode (true=A6 stay-on-base N/A)").option("--vcs-archive-to-base <bool>", "project declares merge-back-to-base archive flow (false=A5 N/A)").option("--vcs-doc-paths <paths>", "B7 exemption paths, comma separated (dirs need trailing /, glob like *.md)").action(async (id, opts) => {
5981
6973
  const client = createClient(opts.url);
6974
+ const parseBool = (v, flag) => {
6975
+ if (v === void 0) return void 0;
6976
+ if (v === "true") return true;
6977
+ if (v === "false") return false;
6978
+ renderResult("json", fail({
6979
+ message: `${flag} \u53EA\u63A5\u53D7 true/false\uFF0C\u6536\u5230\uFF1A${v}`,
6980
+ hint: "\u5E03\u5C14\u65D7\u6807\u663E\u5F0F\u4F20\u503C\uFF08--vcs-enabled true\uFF09\uFF0C\u4E0D\u7528\u88F8\u65D7\u6807\u5F62\u6001",
6981
+ httpStatus: 0
6982
+ }));
6983
+ return "invalid";
6984
+ };
6985
+ const vcsFields = {};
6986
+ let vcsProvided = false;
6987
+ const enabled = parseBool(opts.vcsEnabled, "--vcs-enabled");
6988
+ const worktreeMode = parseBool(opts.vcsWorktreeMode, "--vcs-worktree-mode");
6989
+ const archiveToBase = parseBool(opts.vcsArchiveToBase, "--vcs-archive-to-base");
6990
+ if (enabled === "invalid" || worktreeMode === "invalid" || archiveToBase === "invalid") return;
6991
+ if (enabled !== void 0) {
6992
+ vcsFields.enabled = enabled;
6993
+ vcsProvided = true;
6994
+ }
6995
+ if (opts.vcsRepoPath !== void 0) {
6996
+ vcsFields.repoPath = opts.vcsRepoPath;
6997
+ vcsProvided = true;
6998
+ }
6999
+ if (opts.vcsBaseBranch !== void 0) {
7000
+ vcsFields.baseBranch = opts.vcsBaseBranch;
7001
+ vcsProvided = true;
7002
+ }
7003
+ if (opts.vcsBranchPattern !== void 0) {
7004
+ vcsFields.branchPattern = opts.vcsBranchPattern;
7005
+ vcsProvided = true;
7006
+ }
7007
+ if (worktreeMode !== void 0) {
7008
+ vcsFields.worktreeMode = worktreeMode;
7009
+ vcsProvided = true;
7010
+ }
7011
+ if (archiveToBase !== void 0) {
7012
+ vcsFields.archiveToBase = archiveToBase;
7013
+ vcsProvided = true;
7014
+ }
7015
+ if (opts.vcsDocPaths !== void 0) {
7016
+ vcsFields.docPaths = opts.vcsDocPaths.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
7017
+ vcsProvided = true;
7018
+ }
5982
7019
  const patch = {
5983
7020
  ...opts.name !== void 0 ? { name: opts.name } : {},
5984
- ...opts.description !== void 0 ? { description: opts.description } : {}
7021
+ ...opts.description !== void 0 ? { description: opts.description } : {},
7022
+ ...vcsProvided ? { vcs: vcsFields } : {}
5985
7023
  };
5986
7024
  const result = await handleUpdateProject(client, id, patch);
5987
7025
  renderResult("json", result);
@@ -5999,7 +7037,8 @@ function projectFields(p) {
5999
7037
  ["key", p.key],
6000
7038
  ["name", p.name],
6001
7039
  ["status", p.status],
6002
- ["description", p.description]
7040
+ ["description", p.description],
7041
+ ["vcs", p.vcs]
6003
7042
  ];
6004
7043
  }
6005
7044
 
@@ -6921,13 +7960,13 @@ function renderReport(report, format) {
6921
7960
 
6922
7961
  // src/commands/install/command.ts
6923
7962
  import { Command as Command12 } from "commander";
6924
- import { existsSync as existsSync8 } from "fs";
7963
+ import { existsSync as existsSync9 } from "fs";
6925
7964
  import { createInterface as createInterface2 } from "readline/promises";
6926
7965
  import { dirname as dirname5, join as join9 } from "path";
6927
7966
  import { AGENT_MAIN_DOC as AGENT_MAIN_DOC5, SKILL_MAIN_DOC as SKILL_MAIN_DOC5, VERSION as VERSION3 } from "@siming-org/core";
6928
7967
 
6929
7968
  // src/commands/install/writer.ts
6930
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync15, renameSync as renameSync4, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
7969
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync15, renameSync as renameSync4, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
6931
7970
  import { dirname as dirname4, join as join8 } from "path";
6932
7971
  import { homedir as homedir4 } from "os";
6933
7972
  import { parse as parseYaml3, stringify as stringifyYaml } from "yaml";
@@ -6999,7 +8038,7 @@ function permissionsToMatrix(permissions) {
6999
8038
  }
7000
8039
  return matrix;
7001
8040
  }
7002
- var AGENT_INFO_BOUNDARY = "\u4EFB\u52A1\u4F9D\u636E\u9650\u4E8E\u9879\u76EE\u4EE3\u7801\u3001\u8BBE\u8BA1\u6587\u6863/\u6267\u884C\u62A5\u544A\u53CA\u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08\u542B\u4EFB\u52A1\u4FE1\u606F\u3001\u63D0\u793A\u8BCD\u3001\u59D4\u6D3E\u6750\u6599\uFF09\uFF1B\u7981\u6B62\u81EA\u884C\u8BFB\u53D6\u65E5\u5FD7\u3001\u4E34\u65F6\u6587\u4EF6\u7B49\u8FC7\u7A0B\u4EA7\u7269\uFF0C\u7981\u6B62\u81EA\u884C\u6267\u884C\u6D4B\u8BD5\u3001\u547D\u4EE4\u6216\u5176\u4ED6\u64CD\u4F5C\uFF0C\u59D4\u6D3E\u4EFB\u52A1\u660E\u786E\u8981\u6C42\u65F6\u9664\u5916\u3002";
8041
+ var AGENT_INFO_BOUNDARY = "\u4EFB\u52A1\u4F9D\u636E\u9650\u4E8E\u9879\u76EE\u4EE3\u7801\u3001\u8BBE\u8BA1\u6587\u6863/\u6267\u884C\u62A5\u544A\u53CA\u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08\u542B\u4EFB\u52A1\u4FE1\u606F\u3001\u63D0\u793A\u8BCD\u3001\u59D4\u6D3E\u6750\u6599\uFF09\uFF1B\u4E0D\u4FEE\u6539\u4EE3\u7801\u6216\u6587\u6863\uFF0C\u4E0D\u6267\u884C\u6D4B\u8BD5\u3001\u6784\u5EFA\u6216\u53D1\u5E03\u547D\u4EE4\uFF1B\u81EA\u8EAB\u89D2\u8272\u5B9A\u4E49\u6388\u6743\u7684\u91C7\u8BC1\u64CD\u4F5C\uFF08\u5982\u6D4F\u89C8\u5668\u4EA4\u4E92\u3001\u622A\u56FE\u53D6\u8BC1\u3001\u8BFB\u53D6\u8FD0\u884C\u8BC1\u636E\uFF09\u4E0D\u53D7\u6B64\u9650\uFF0C\u5176\u4F59\u8FC7\u7A0B\u4EA7\u7269\u8BFB\u53D6\u4E0E\u547D\u4EE4\u6267\u884C\u4ECD\u7981\u6B62\u3002";
7003
8042
  function agentToMarkdown(agent, realModel) {
7004
8043
  const fm = {
7005
8044
  name: agent.name,
@@ -7028,7 +8067,7 @@ function agentTargetPath(dir, agent) {
7028
8067
  return join8(dir, "agents", agent.name, "agent.md");
7029
8068
  }
7030
8069
  function readSimingVersion(path) {
7031
- if (!existsSync7(path)) return null;
8070
+ if (!existsSync8(path)) return null;
7032
8071
  let text;
7033
8072
  try {
7034
8073
  text = readFileSync15(path, "utf-8");
@@ -7053,7 +8092,7 @@ function isPlainObject(v) {
7053
8092
  return typeof v === "object" && v !== null && !Array.isArray(v);
7054
8093
  }
7055
8094
  function readMcpConfig(path) {
7056
- if (!existsSync7(path)) return { state: "absent" };
8095
+ if (!existsSync8(path)) return { state: "absent" };
7057
8096
  let text;
7058
8097
  try {
7059
8098
  text = readFileSync15(path, "utf-8");
@@ -7252,7 +8291,15 @@ async function buildInstallPlan(client, scope, projectId, only) {
7252
8291
  if (!agentsR.success) return agentsR;
7253
8292
  const t = resolveInstallTargets(scope);
7254
8293
  if (t.error || !t.targets) {
7255
- return { success: false, error: { message: t.error ?? "cannot resolve install targets", httpStatus: 0 } };
8294
+ return {
8295
+ success: false,
8296
+ error: {
8297
+ message: t.error ?? "cannot resolve install targets",
8298
+ httpStatus: 0,
8299
+ // T202609190002:错误四要素契约补齐(hint 缺失导致 E2E_CLI_WS_INSTALL_002 失败)
8300
+ hint: "run `siming init --project <key|id>` inside the project workspace (containing .siming/), or retry with --scope global"
8301
+ }
8302
+ };
7256
8303
  }
7257
8304
  const dir = t.targets.dir;
7258
8305
  const items = [];
@@ -7285,7 +8332,7 @@ function compareSkill(s, dir) {
7285
8332
  if (!isSafeAssetName(s.name)) {
7286
8333
  return { ...base, action: "conflict", reason: `unsafe asset name "${s.name}"` };
7287
8334
  }
7288
- if (!existsSync8(path)) return { ...base, action: "install", reason: "not installed locally" };
8335
+ if (!existsSync9(path)) return { ...base, action: "install", reason: "not installed locally" };
7289
8336
  const marked = readSimingVersion(path);
7290
8337
  if (marked === null) {
7291
8338
  return { ...base, localVersion: "unknown", action: "conflict", reason: "no siming-version marker (third-party asset; use --force to overwrite)" };
@@ -7303,7 +8350,7 @@ function compareAgent(a, dir) {
7303
8350
  if (!isSafeAssetName(a.name)) {
7304
8351
  return { ...base, action: "conflict", reason: `unsafe asset name "${a.name}"` };
7305
8352
  }
7306
- if (!existsSync8(path)) return { ...base, action: "install", reason: "not installed locally" };
8353
+ if (!existsSync9(path)) return { ...base, action: "install", reason: "not installed locally" };
7307
8354
  const marked = readSimingVersion(path);
7308
8355
  if (marked === null) {
7309
8356
  return { ...base, localVersion: "unknown", action: "conflict", reason: "no siming-version marker (third-party asset; use --force to overwrite)" };
@@ -7396,7 +8443,7 @@ async function applyInstall(client, scope, projectId, items, opts) {
7396
8443
  results.push({ name: item.name, type: "skill", action: "failed", reason: `invalid reference doc from server: ${invalidRef}`, hint: "\u5F15\u7528\u6587\u6863\u975E\u6CD5\uFF08\u6570\u636E\u53EF\u80FD\u88AB\u7ED5\u8FC7\u6821\u9A8C\u5199\u5165\uFF09\uFF1B\u4FEE\u6B63 server \u6570\u636E\u540E\u91CD\u8BD5\uFF0C\u672C\u6761\u672A\u5199\u5165" });
7397
8444
  continue;
7398
8445
  }
7399
- const targetExists = existsSync8(targetPath);
8446
+ const targetExists = existsSync9(targetPath);
7400
8447
  writeAssetDirectory(dirname5(targetPath), [
7401
8448
  { relPath: SKILL_MAIN_DOC5, content: skillToMarkdown(r.data) },
7402
8449
  ...(r.data.references ?? []).map((ref) => ({ relPath: ref.path, content: ref.content }))
@@ -7432,7 +8479,7 @@ async function applyInstall(client, scope, projectId, items, opts) {
7432
8479
  results.push({ name: item.name, type: "agent", action: "failed", reason: `invalid reference doc from server: ${invalidRef}`, hint: "\u5F15\u7528\u6587\u6863\u975E\u6CD5\uFF08\u6570\u636E\u53EF\u80FD\u88AB\u7ED5\u8FC7\u6821\u9A8C\u5199\u5165\uFF09\uFF1B\u4FEE\u6B63 server \u6570\u636E\u540E\u91CD\u8BD5\uFF0C\u672C\u6761\u672A\u5199\u5165" });
7433
8480
  continue;
7434
8481
  }
7435
- const targetExists = existsSync8(targetPath);
8482
+ const targetExists = existsSync9(targetPath);
7436
8483
  writeAssetDirectory(dirname5(targetPath), [
7437
8484
  { relPath: AGENT_MAIN_DOC5, content: agentToMarkdown(r.data, realModel) },
7438
8485
  ...(r.data.references ?? []).map((ref) => ({ relPath: ref.path, content: ref.content }))
@@ -7549,7 +8596,7 @@ var PHASE2_NEXT_STEPS = `\u8D44\u4EA7\u5DF2\u5C31\u7EEA\uFF08\u5168\u5C40\u8D44\
7549
8596
  \uFF08siming task create --template <\u6A21\u677Fcode> --track <\u8F68\u9053> --project <\u9879\u76EEkey> --title "<\u6807\u9898>"\uFF09
7550
8597
  \u5E76\u8BFB\u53D6\u9996\u8282\u70B9\u4E0A\u4E0B\u6587\uFF1Asiming task context <taskId>`;
7551
8598
  var MCP_TOOLING_TEXT = `\u63A5\u53E3\u5206\u5DE5\u73B0\u72B6\uFF08\u6743\u5A01\uFF09\uFF1A
7552
- - MCP \u4EC5\u4E09\u4E2A\u5DE5\u5177\uFF1Asiming_task\uFF08\u4EFB\u52A1\u751F\u547D\u5468\u671F\u8BFB\u5199\uFF0C22 \u4E2A action\uFF09\u3001siming_template\uFF08\u53EA\u8BFB\u6A21\u677F\u67E5\u8BE2\uFF0Clist/show\uFF09\u3001siming_auth\uFF08\u8EAB\u4EFD\u67E5\u8BE2\uFF0Cwhoami\uFF09\uFF1B
8599
+ - MCP \u4EC5\u4E09\u4E2A\u5DE5\u5177\uFF1Asiming_task\uFF08\u4EFB\u52A1\u751F\u547D\u5468\u671F\u8BFB\u5199\uFF0C24 \u4E2A action\uFF09\u3001siming_template\uFF08\u53EA\u8BFB\u6A21\u677F\u67E5\u8BE2\uFF0Clist/show\uFF09\u3001siming_auth\uFF08\u8EAB\u4EFD\u67E5\u8BE2\uFF0Cwhoami\uFF09\uFF1B
7553
8600
  - \u8D44\u4EA7\u3001\u9879\u76EE\u3001\u6A21\u677F\u5199\u5165/\u5BFC\u5165/\u5347\u7EA7\u3001\u521D\u59CB\u5316\u3001\u5B89\u88C5\u3001\u8BCA\u65AD\u7B49\u4E00\u5207\u8D44\u4EA7\u7BA1\u7406\u4E0E\u8FD0\u7EF4\u64CD\u4F5C\u4E00\u5F8B\u8D70 CLI \u547D\u4EE4\u3002
7554
8601
 
7555
8602
  \u5BBF\u4E3B\u517C\u5BB9\u6027\u81EA\u68C0\u4E0E\u9002\u914D\uFF08\u63A5\u5165\u540E\u5FC5\u505A\uFF09\uFF1A
@@ -8045,7 +9092,7 @@ import { VERSION as VERSION4, SIMING_CONFIG_KEYS as SIMING_CONFIG_KEYS2 } from "
8045
9092
 
8046
9093
  // src/commands/server/daemon.ts
8047
9094
  import { execFileSync } from "child_process";
8048
- import { closeSync, existsSync as existsSync9, lstatSync, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync16, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
9095
+ import { closeSync, existsSync as existsSync10, lstatSync, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync16, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
8049
9096
  import { join as join10, resolve as resolve2 } from "path";
8050
9097
  import { spawn } from "child_process";
8051
9098
 
@@ -8256,7 +9303,7 @@ function readLogTail(lines = 30) {
8256
9303
  }
8257
9304
  function spawnDaemon(configEnv) {
8258
9305
  const entry = resolve2(process.argv[1] ?? "");
8259
- if (entry.length === 0 || !existsSync9(entry)) {
9306
+ if (entry.length === 0 || !existsSync10(entry)) {
8260
9307
  throw new Error(`\u65E0\u6CD5\u5B9A\u4F4D siming \u5165\u53E3\u811A\u672C\uFF08process.argv[1]=${process.argv[1] ?? "(\u7A7A)"}\uFF09`);
8261
9308
  }
8262
9309
  if (entry.endsWith(".ts")) {
@@ -8704,7 +9751,7 @@ function parseDeclarationsMap(spec) {
8704
9751
  }
8705
9752
  function createIssueCommand() {
8706
9753
  const issue = new Command18("issue").description("Issue pool: record \u2192 organize \u2192 convert to tasks");
8707
- withCommonOpts(issue.command("add"), "fancy").description("Record an issue into the pool (source=cli)").argument("[title]", "issue title (or use --title)").option("--title <t>", "issue title (1-200 chars)").option("--description <d>", "issue description (optional)").option("--project <key|id>", "projectId (key|id; falls back to workspace defaultProject, then server default project)").action(async (titleArg, opts) => {
9754
+ withCommonOpts(issue.command("add"), "fancy").description("Record an issue into the pool (source=cli; requires --approved human approval quote)").argument("[title]", "issue title (or use --title)").option("--title <t>", "issue title (1-200 chars)").option("--description <d>", "issue description (optional)").option("--approved <text>", `human approval quote verbatim (1-500 chars; e.g. user's literal reply "OK, record it")`).option("--project <key|id>", "projectId (key|id; falls back to workspace defaultProject, then server default project)").action(async (titleArg, opts) => {
8708
9755
  const client = createClient(opts.url);
8709
9756
  const title = opts.title ?? titleArg;
8710
9757
  if (title === void 0 || title.length === 0) {
@@ -8714,6 +9761,26 @@ function createIssueCommand() {
8714
9761
  });
8715
9762
  return;
8716
9763
  }
9764
+ if (opts.approved === void 0 || opts.approved.trim() === "") {
9765
+ renderResult("json", {
9766
+ success: false,
9767
+ error: {
9768
+ message: 'issue add \u987B\u643A\u5E26 --approved "<\u4EBA\u7C7B\u5BA1\u6279\u539F\u8BDD>"\uFF08\u5982\u7528\u6237\u56DE\u590D\u7684\u300COK\uFF0C\u8BB0\u5F55\u300D\u539F\u6587\u9010\u5B57\uFF09\u2014\u2014AI \u4E0D\u5F97\u672A\u7ECF\u7528\u6237\u6279\u51C6\u5F55\u5165 issue\uFF0C\u4E5F\u4E0D\u5F97\u4EE3\u586B/\u7F16\u9020\u5BA1\u6279\u539F\u8BDD',
9769
+ httpStatus: 0
9770
+ }
9771
+ });
9772
+ return;
9773
+ }
9774
+ if (opts.approved.length > 500) {
9775
+ renderResult("json", {
9776
+ success: false,
9777
+ error: {
9778
+ message: `--approved \u8D85\u51FA 500 \u5B57\u4E0A\u9650\uFF08\u5F53\u524D ${String(opts.approved.length)} \u5B57\uFF09\u2014\u2014\u5BA1\u6279\u539F\u8BDD\u987B\u4E3A\u7528\u6237\u539F\u6587\u4E14 \u2264500 \u5B57\uFF0C\u53EF\u8BF7\u7528\u6237\u7ED9\u51FA\u7B80\u77ED\u660E\u786E\u7684\u6279\u51C6\u8868\u8FF0\u540E\u5F15\u7528\u5176\u539F\u6587`,
9779
+ httpStatus: 0
9780
+ }
9781
+ });
9782
+ return;
9783
+ }
8717
9784
  const projectR = await resolveScopedProject(client, opts.project, "ws-default", { commandLabel: "siming issue add" });
8718
9785
  if (!projectR.success) {
8719
9786
  renderResult(opts.format ?? "fancy", projectR);
@@ -8723,11 +9790,12 @@ function createIssueCommand() {
8723
9790
  const result = await handleCreateIssue(client, {
8724
9791
  title,
8725
9792
  ...opts.description !== void 0 ? { description: opts.description } : {},
8726
- ...projectId !== void 0 ? { projectId } : {}
9793
+ ...projectId !== void 0 ? { projectId } : {},
9794
+ approved: opts.approved
8727
9795
  });
8728
9796
  renderResult(opts.format ?? "fancy", result);
8729
9797
  });
8730
- withCommonOpts(issue.command("list"), "table").option("--status <s>", "filter by issue status (pending|partial|converted|cancelled)").option("--source <s>", "filter by source channel (mcp|cli|web)").option("--project <key|id>", "filter by projectId (key|id)").option("--task <taskId>", "filter by linked task").option("--q <keyword>", "search in title/description").option("--view <v>", "view: all|open (default all; open = \u6709\u6548\u672A\u8DDF\u8FDB\u5B8C)").option("--page <n>", "page number (default 1)", "1").option("--limit <n>", `page size (default ${PAGE_DEFAULT2}, max ${PAGE_MAX2})`).option("--all", `show all (limit ${PAGE_MAX2})`).description("List issues (with progress aggregation done/cancelled/total)").action(async (opts) => {
9798
+ withCommonOpts(issue.command("list"), "table").option("--status <s>", "filter by issue status (pending|partial|converted|cancelled|done)").option("--source <s>", "filter by source channel (mcp|cli|web)").option("--project <key|id>", "filter by projectId (key|id)").option("--task <taskId>", "filter by linked task").option("--q <keyword>", "search in title/description").option("--view <v>", "view: all|open (default all; open = \u6709\u6548\u672A\u8DDF\u8FDB\u5B8C)").option("--page <n>", "page number (default 1)", "1").option("--limit <n>", `page size (default ${PAGE_DEFAULT2}, max ${PAGE_MAX2})`).option("--all", `show all (limit ${PAGE_MAX2})`).description("List issues (with progress aggregation done/cancelled/total)").action(async (opts) => {
8731
9799
  const client = createClient(opts.url);
8732
9800
  const filters = {};
8733
9801
  if (opts.status) filters.status = opts.status;
@@ -8829,6 +9897,11 @@ function createIssueCommand() {
8829
9897
  const result = await handleCancelIssue(client, issueId, opts.reason);
8830
9898
  renderResult(opts.format ?? "fancy", result);
8831
9899
  });
9900
+ withCommonOpts(issue.command("done"), "fancy").argument("<issueId>", "issue id (I-prefixed)").option("--note <n>", "completion note (optional, \u2264500 chars \u2014 e.g. \u5728\u54EA\u4E2A\u4EFB\u52A1/\u54EA\u6B21\u6539\u52A8\u4E2D\u987A\u5E26\u89E3\u51B3)").description("Mark an issue as done directly (terminal state; keep taskLinks)").action(async (issueId, opts) => {
9901
+ const client = createClient(opts.url);
9902
+ const result = await handleDoneIssue(client, issueId, opts.note);
9903
+ renderResult(opts.format ?? "fancy", result);
9904
+ });
8832
9905
  withCommonOpts(issue.command("convert"), "fancy").requiredOption("--issue-ids <ids>", "comma-separated issue ids (or repeatable flag)", collectArgs, []).option("--declaration <mode>", "uniform declaration for all issues: all|partial (default all)").option("--declarations <map>", "per-issue declarations: id1:all,id2:partial (overrides --declaration)").requiredOption("--title <t>", "new task title").requiredOption("--template <code|name|id>", "DAG template code (preferred), name, or 24-hex id").requiredOption("--track <t>", "task track (backend|ui|mixed|research)").option("--type <t>", "task type (feature|bugfix|ui-tweak|research)").option("--skip <ids>", "node ids to prune (comma-separated, repeatable)", collectArgs, []).option("--project <key|id>", "projectId (key|id; issues and new task must belong to it)").description("Convert issues to a task (single group: N issues \u2192 1 task; split = multiple calls)").action(async (opts) => {
8833
9906
  const client = createClient(opts.url);
8834
9907
  const issueIds = [