@siming-org/cli 0.8.0 → 0.8.2

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;
@@ -1439,6 +2209,8 @@ var taskArgs = {
1439
2209
  "context",
1440
2210
  "node",
1441
2211
  "advance",
2212
+ // 门禁只读预检(T202609230001:advance 前自查节点门禁,零写入)
2213
+ "check",
1442
2214
  "approve",
1443
2215
  "pause",
1444
2216
  "resume",
@@ -1481,10 +2253,13 @@ var taskArgs = {
1481
2253
  view: hostNeutralEnum(z3.enum(["prompt", "skills", "basic", "full"])).optional(),
1482
2254
  nodeId: z3.string().optional(),
1483
2255
  // advance / record-set(note 写 history;summary 写 nodeRecords,两位独立);
1484
- // finalize 为 advance 收尾确认(终节点终态化必需,中间节点无副作用——T202609130009)
2256
+ // finalize 为 advance 收尾确认(终节点终态化必需,中间节点无副作用——T202609130009);
2257
+ // force/forceReason 成对(T202609230001:门禁强制推进,仅凭用户明示指令,AI 禁自决)
1485
2258
  note: z3.string().optional(),
1486
2259
  summary: z3.string().optional(),
1487
2260
  finalize: z3.boolean().optional(),
2261
+ force: z3.boolean().optional(),
2262
+ forceReason: z3.string().optional(),
1488
2263
  // approve
1489
2264
  decision: hostNeutralEnum(z3.enum(["approved", "rejected"])).optional(),
1490
2265
  comment: z3.string().optional(),
@@ -1496,6 +2271,21 @@ var taskArgs = {
1496
2271
  item: z3.string().optional(),
1497
2272
  checkId: z3.string().optional(),
1498
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(),
1499
2289
  artifactType: hostNeutralEnum(z3.enum(ARTIFACT_TYPES2)).optional(),
1500
2290
  path: z3.string().optional(),
1501
2291
  file: z3.string().optional(),
@@ -1512,7 +2302,14 @@ var taskArgs = {
1512
2302
  trackNote: z3.string().optional(),
1513
2303
  text: z3.string().optional(),
1514
2304
  // 批量形态(T202609010002):与对应单条参数二选一;元素字段级校验归 server 分区步(部分失败保留成功条目)
1515
- 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(),
1516
2313
  artifactEntries: z3.array(
1517
2314
  z3.object({
1518
2315
  artifactType: z3.enum(ARTIFACT_TYPES2),
@@ -1650,7 +2447,8 @@ var TASK_FIELD_SPECS = {
1650
2447
  query: { required: [], optional: ["taskId", "status", "track", "projectId", "page", "limit"] },
1651
2448
  context: { required: ["taskId"], optional: ["view"] },
1652
2449
  node: { required: ["taskId", "view"], optional: ["nodeId"] },
1653
- advance: { required: ["taskId"], optional: ["note", "summary", "finalize"] },
2450
+ advance: { required: ["taskId"], optional: ["note", "summary", "finalize", "force", "forceReason"] },
2451
+ check: { required: ["taskId"], optional: [] },
1654
2452
  approve: { required: ["taskId", "decision"], optional: ["comment"] },
1655
2453
  pause: { required: ["taskId"], optional: ["reason"] },
1656
2454
  resume: { required: ["taskId"], optional: ["resumeNote"] },
@@ -1658,9 +2456,12 @@ var TASK_FIELD_SPECS = {
1658
2456
  reopen: { required: ["taskId", "reason"], optional: [] },
1659
2457
  history: { required: ["taskId"], optional: [] },
1660
2458
  instance: { required: ["taskId"], optional: [] },
1661
- "record-set": { required: ["taskId", "node", "summary"], optional: [] },
1662
- "record-check-add": { required: ["taskId", "node"], optional: ["item", "passed", "checkItems"] },
1663
- "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"] },
1664
2465
  "record-artifact-add": {
1665
2466
  required: ["taskId", "node"],
1666
2467
  optional: ["artifactType", "path", "file", "content", "note", "artifactEntries"]
@@ -1688,7 +2489,7 @@ function createMcpServer() {
1688
2489
  const client = createClient(void 0, "mcp");
1689
2490
  server.tool(
1690
2491
  "siming_task",
1691
- "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",
1692
2493
  taskArgs,
1693
2494
  async (rawArgs) => {
1694
2495
  const masked = applyFieldMask(rawArgs, TASK_FIELD_SPECS);
@@ -1776,7 +2577,19 @@ function createMcpServer() {
1776
2577
  case "advance": {
1777
2578
  const { taskId } = args;
1778
2579
  if (taskId === void 0) return requireError("advance", ["taskId"], args);
1779
- 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));
1780
2593
  }
1781
2594
  case "approve": {
1782
2595
  const { taskId, decision } = args;
@@ -1818,11 +2631,26 @@ function createMcpServer() {
1818
2631
  return mcpContent(await handleGetInstance(client, taskId));
1819
2632
  }
1820
2633
  case "record-set": {
1821
- const { taskId, node, summary } = args;
1822
- if (taskId === void 0 || node === void 0 || summary === void 0) {
1823
- 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);
1824
2637
  }
1825
- return mcpContent(await handleRecordSummarySet(client, taskId, node, summary));
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
+ );
2652
+ }
2653
+ return mcpContent(await handleRecordSummarySet(client, taskId, node, fields));
1826
2654
  }
1827
2655
  case "record-check-add": {
1828
2656
  const { taskId, node } = args;
@@ -1842,7 +2670,13 @@ function createMcpServer() {
1842
2670
  if (taskId === void 0 || node === void 0 || item === void 0) {
1843
2671
  return requireError("record-check-add", ["taskId", "node", "item"], args);
1844
2672
  }
1845
- 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
+ );
1846
2680
  }
1847
2681
  case "record-check-patch": {
1848
2682
  const { taskId, node, checkId } = args;
@@ -1850,7 +2684,13 @@ function createMcpServer() {
1850
2684
  return requireError("record-check-patch", ["taskId", "node", "checkId"], args);
1851
2685
  }
1852
2686
  return mcpContent(
1853
- 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
+ })
1854
2694
  );
1855
2695
  }
1856
2696
  case "record-artifact-add": {
@@ -2438,9 +3278,29 @@ function createTaskCommand() {
2438
3278
  }
2439
3279
  renderResult("json", result);
2440
3280
  });
2441
- 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
+ }
2442
3290
  const client = createClient(opts.url);
2443
- const result = await handleAdvance(client, taskId, opts.note, opts.summary, opts.finalize === true);
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) => {
3302
+ const client = createClient(opts.url);
3303
+ const result = await handleTaskCheck(client, taskId);
2444
3304
  renderResult(opts.format, result);
2445
3305
  });
2446
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) => {
@@ -2575,17 +3435,60 @@ function createTaskCommand() {
2575
3435
  renderResult(opts.format, result, { fields: result.success ? nodeFields(result.data) : [] });
2576
3436
  });
2577
3437
  const record = task.command("record").description("Node execution records \u2014 summary/checks/artifacts/confirmations/decisions/review (draft-writable, small-step append)");
2578
- 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) => {
2579
- const client = createClient(opts.url);
2580
- const result = await handleRecordSummarySet(client, taskId, opts.node, opts.summary);
2581
- if (result.success) {
2582
- renderResult(opts.format, result, { successMessage: `${taskId} \u8282\u70B9 ${opts.node} summary \u5DF2\u5199\u5165` });
2583
- } else {
2584
- 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
+ }
2585
3488
  }
2586
- });
3489
+ );
2587
3490
  const check = record.command("check").description("Completion-check items (server-generated short ids)");
2588
- 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) => {
2589
3492
  const client = createClient(opts.url);
2590
3493
  const items = opts.item ?? [];
2591
3494
  const rawEntries = opts.entry ?? [];
@@ -2607,7 +3510,14 @@ function createTaskCommand() {
2607
3510
  return;
2608
3511
  }
2609
3512
  if (!batchMode) {
2610
- 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
+ });
2611
3521
  if (result2.success) {
2612
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` });
2613
3523
  } else {
@@ -2631,12 +3541,25 @@ function createTaskCommand() {
2631
3541
  localFailures.push({ index: submitIndex, message: "passed \u987B\u4E3A\u5E03\u5C14" });
2632
3542
  return;
2633
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
+ }
2634
3552
  entryPairs.push({
2635
- 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
+ },
2636
3559
  rawIndex: i
2637
3560
  });
2638
3561
  } catch {
2639
- 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":"..."}' });
2640
3563
  }
2641
3564
  });
2642
3565
  const submitted = [...itemElements, ...entryPairs.map((p) => p.entry)];
@@ -2653,7 +3576,7 @@ function createTaskCommand() {
2653
3576
  });
2654
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) => {
2655
3578
  const client = createClient(opts.url);
2656
- 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 });
2657
3580
  if (result.success) {
2658
3581
  renderResult(opts.format, result, { successMessage: `${taskId} \u8282\u70B9 ${opts.node} check ${opts.check} \u2192 ${opts.failed ? "failed" : "passed"}` });
2659
3582
  } else {
@@ -2973,7 +3896,7 @@ import { Command as Command3 } from "commander";
2973
3896
  import { SKILL_MAIN_DOC, SkillScopeSchema } from "@siming-org/core";
2974
3897
 
2975
3898
  // src/client/scaffold-templates.ts
2976
- import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync11 } from "fs";
3899
+ import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync11 } from "fs";
2977
3900
  import { dirname as dirname3, join as join6 } from "path";
2978
3901
  import { fileURLToPath } from "url";
2979
3902
  var SCAFFOLD_DIR = join6("templates", "project-skills");
@@ -2982,7 +3905,7 @@ function resolveCliPackageRoot() {
2982
3905
  let dir = dirname3(fileURLToPath(import.meta.url));
2983
3906
  for (let depth = 0; depth < 10; depth += 1) {
2984
3907
  const pkgFile = join6(dir, "package.json");
2985
- if (existsSync6(pkgFile)) {
3908
+ if (existsSync7(pkgFile)) {
2986
3909
  try {
2987
3910
  const pkg = JSON.parse(readFileSync11(pkgFile, "utf-8"));
2988
3911
  if (pkg.name === "@siming-org/cli") return ok2(dir);
@@ -4295,6 +5218,7 @@ import { Command as Command6 } from "commander";
4295
5218
  import { SkillScopeSchema as SkillScopeSchema2 } from "@siming-org/core";
4296
5219
 
4297
5220
  // src/client/handlers/node.ts
5221
+ import { assertUniqueAiCheckIds } from "@siming-org/core";
4298
5222
  async function handleListNodePresets(client, query = {}) {
4299
5223
  const qs = new URLSearchParams();
4300
5224
  if (query.projectId) qs.set("projectId", query.projectId);
@@ -4426,6 +5350,8 @@ ${failures.join("\n")}`,
4426
5350
  prompt: preset.prompt,
4427
5351
  skills: [...preset.skills],
4428
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 })) } : {},
4429
5355
  sourcePreset: {
4430
5356
  code: preset.code,
4431
5357
  version: preset.version,
@@ -4433,6 +5359,18 @@ ${failures.join("\n")}`,
4433
5359
  agents: [...preset.agents ?? []]
4434
5360
  }
4435
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
+ }
4436
5374
  const nodeIds = new Set(nodes.map((n) => n.id));
4437
5375
  const danglingEdges = composition.edges.filter((e) => !nodeIds.has(e.from) || !nodeIds.has(e.to)).map((e) => `${e.from}\u2192${e.to}`);
4438
5376
  if (danglingEdges.length > 0) {
@@ -4553,7 +5491,7 @@ function createNodeCommand() {
4553
5491
  };
4554
5492
  renderResult("json", await handleCreateNodePreset(client, input));
4555
5493
  });
4556
- 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) => {
4557
5495
  const client = createClient(opts.url);
4558
5496
  let prompt;
4559
5497
  if (opts.promptFile !== void 0) {
@@ -4569,6 +5507,30 @@ function createNodeCommand() {
4569
5507
  } else if (opts.prompt !== void 0) {
4570
5508
  prompt = opts.prompt;
4571
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
+ }
4572
5534
  if (opts.skill !== void 0 && opts.clearSkills) {
4573
5535
  renderResult("json", fail({ message: "--skill \u4E0E --clear-skills \u4E92\u65A5", httpStatus: 0 }));
4574
5536
  return;
@@ -4586,10 +5548,11 @@ function createNodeCommand() {
4586
5548
  ...opts.skill !== void 0 ? { skills: opts.skill } : opts.clearSkills ? { skills: [] } : {},
4587
5549
  ...opts.agent !== void 0 ? { agents: opts.agent } : opts.clearAgents ? { agents: [] } : {},
4588
5550
  ...opts.description !== void 0 ? { description: opts.description } : {},
5551
+ ...checks !== void 0 ? { toolChecks: checks.toolChecks, aiChecks: checks.aiChecks } : {},
4589
5552
  ...opts.newVersion !== void 0 ? { version: opts.newVersion } : {}
4590
5553
  };
4591
5554
  if (Object.keys(patch).length === 0) {
4592
- 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" }));
4593
5556
  return;
4594
5557
  }
4595
5558
  renderResult("json", await handleUpdateNodePreset(client, code, patch));
@@ -4653,11 +5616,11 @@ function nodeFields2(n) {
4653
5616
  }
4654
5617
 
4655
5618
  // src/commands/flow/command.ts
4656
- import { execFile } from "child_process";
5619
+ import { execFile as execFile2 } from "child_process";
4657
5620
  import { mkdtempSync, rmSync as rmSync2 } from "fs";
4658
5621
  import { tmpdir } from "os";
4659
5622
  import { join as join7 } from "path";
4660
- import { promisify } from "util";
5623
+ import { promisify as promisify2 } from "util";
4661
5624
  import { createInterface } from "readline/promises";
4662
5625
  import { Command as Command7 } from "commander";
4663
5626
  import {
@@ -4674,7 +5637,7 @@ import {
4674
5637
  SKILL_MAIN_DOC as SKILL_MAIN_DOC2,
4675
5638
  createReferencesSchema as createReferencesSchema2
4676
5639
  } from "@siming-org/core";
4677
- var FLOW_MANIFEST_FORMAT_VERSION = "1.1";
5640
+ var FLOW_MANIFEST_FORMAT_VERSION = "1.2";
4678
5641
  var uniqueStrings = (values, ctx, label) => {
4679
5642
  const seen = /* @__PURE__ */ new Set();
4680
5643
  for (let index = 0; index < values.length; index += 1) {
@@ -4693,6 +5656,14 @@ var manifestCapabilityRefSchema = z6.object({
4693
5656
  var capabilityRefsSchema = z6.array(manifestCapabilityRefSchema).superRefine((refs, ctx) => {
4694
5657
  uniqueStrings(refs.map((ref) => ref.name), ctx, "\u80FD\u529B\u540D\u79F0");
4695
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
+ });
4696
5667
  var nodePresetPayloadBaseSchema = z6.object({
4697
5668
  code: z6.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "kebab-case only"),
4698
5669
  label: z6.string().min(1),
@@ -4702,7 +5673,9 @@ var nodePresetPayloadBaseSchema = z6.object({
4702
5673
  prompt: z6.string().min(1),
4703
5674
  skills: capabilityRefsSchema,
4704
5675
  description: z6.string(),
4705
- 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()
4706
5679
  }).strict();
4707
5680
  var nodePresetPayloadSchema = nodePresetPayloadBaseSchema.extend({ agents: capabilityRefsSchema }).strict();
4708
5681
  var referenceDocsSchema = (mainDoc) => createReferencesSchema2(mainDoc);
@@ -4976,7 +5949,7 @@ function compareSemverStrings(a, b) {
4976
5949
  }
4977
5950
 
4978
5951
  // src/commands/flow/command.ts
4979
- var execFileAsync = promisify(execFile);
5952
+ var execFileAsync2 = promisify2(execFile2);
4980
5953
  function createFlowCommand() {
4981
5954
  const flow = new Command7("flow").description("Node library management \u2014 install npm-distributed node packages & compose templates");
4982
5955
  withCommonOpts(flow.command("list"), "table").description("List installed node libraries (registry entries written by `flow install`)").action(async (opts) => {
@@ -5102,7 +6075,7 @@ async function fetchManifest(pkgSpec, registry) {
5102
6075
  }
5103
6076
  };
5104
6077
  try {
5105
- const { stdout } = await execFileAsync(
6078
+ const { stdout } = await execFileAsync2(
5106
6079
  "npm",
5107
6080
  [
5108
6081
  "pack",
@@ -5123,7 +6096,7 @@ async function fetchManifest(pkgSpec, registry) {
5123
6096
  hint: "\u786E\u8BA4 npm \u7248\u672C\u652F\u6301 --json \u8F93\u51FA"
5124
6097
  });
5125
6098
  }
5126
- const { stdout: manifestText } = await execFileAsync(
6099
+ const { stdout: manifestText } = await execFileAsync2(
5127
6100
  "tar",
5128
6101
  ["-xOf", join7(dir, filename), "package/siming-nodes.json"],
5129
6102
  { maxBuffer: MANIFEST_LIMITS.maxChars * 2 + 64 * 1024 }
@@ -5533,6 +6506,8 @@ function diffNode(payload, entity, packageName) {
5533
6506
  if (entity.version !== payload.version) diffs.push("version");
5534
6507
  if ((entity.description ?? "") !== payload.description) diffs.push("description");
5535
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");
5536
6511
  return diffs;
5537
6512
  }
5538
6513
  function sameOrdered(left, right) {
@@ -5584,6 +6559,9 @@ function snapshotNode(entity) {
5584
6559
  prompt: entity.prompt,
5585
6560
  skills: [...entity.skills].sort(),
5586
6561
  agents: [...entity.agents ?? []].sort(),
6562
+ // T202609230001:门禁声明入快照(plan→apply 窗口期门禁被改应触发 FLOW_INSTALL_STALE 而非静默覆盖)
6563
+ toolChecks: entity.toolChecks ?? [],
6564
+ aiChecks: entity.aiChecks ?? [],
5587
6565
  scope: entity.scope,
5588
6566
  projectId: entity.projectId ?? null,
5589
6567
  description: entity.description ?? null,
@@ -5813,6 +6791,9 @@ async function writeAsset(client, manifest, plan, item) {
5813
6791
  description: payload.description,
5814
6792
  version: payload.version,
5815
6793
  source: { package: manifest.name },
6794
+ // T202609230001:门禁声明随安装写入(缺此两行 = 安装成功但门禁静默丢失)
6795
+ ...payload.toolChecks !== void 0 ? { toolChecks: payload.toolChecks } : {},
6796
+ ...payload.aiChecks !== void 0 ? { aiChecks: payload.aiChecks } : {},
5816
6797
  ...item.targetProjectId !== void 0 ? { projectId: item.targetProjectId } : {}
5817
6798
  };
5818
6799
  return handleCreateNodePreset(client, input);
@@ -5827,7 +6808,12 @@ async function writeAsset(client, manifest, plan, item) {
5827
6808
  agents: payload.agents.map((ref) => ref.name),
5828
6809
  description: payload.description,
5829
6810
  version: payload.version,
5830
- 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 ?? []
5831
6817
  };
5832
6818
  return handleUpdateNodePreset(client, payload.code, patch);
5833
6819
  }
@@ -5983,11 +6969,57 @@ function createProjectCommand() {
5983
6969
  const result = await handleGetProject(client, id);
5984
6970
  renderResult(opts.format, result, { fields: result.success ? projectFields(result.data) : [] });
5985
6971
  });
5986
- 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) => {
5987
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
+ }
5988
7019
  const patch = {
5989
7020
  ...opts.name !== void 0 ? { name: opts.name } : {},
5990
- ...opts.description !== void 0 ? { description: opts.description } : {}
7021
+ ...opts.description !== void 0 ? { description: opts.description } : {},
7022
+ ...vcsProvided ? { vcs: vcsFields } : {}
5991
7023
  };
5992
7024
  const result = await handleUpdateProject(client, id, patch);
5993
7025
  renderResult("json", result);
@@ -6005,7 +7037,8 @@ function projectFields(p) {
6005
7037
  ["key", p.key],
6006
7038
  ["name", p.name],
6007
7039
  ["status", p.status],
6008
- ["description", p.description]
7040
+ ["description", p.description],
7041
+ ["vcs", p.vcs]
6009
7042
  ];
6010
7043
  }
6011
7044
 
@@ -6927,13 +7960,13 @@ function renderReport(report, format) {
6927
7960
 
6928
7961
  // src/commands/install/command.ts
6929
7962
  import { Command as Command12 } from "commander";
6930
- import { existsSync as existsSync8 } from "fs";
7963
+ import { existsSync as existsSync9 } from "fs";
6931
7964
  import { createInterface as createInterface2 } from "readline/promises";
6932
7965
  import { dirname as dirname5, join as join9 } from "path";
6933
7966
  import { AGENT_MAIN_DOC as AGENT_MAIN_DOC5, SKILL_MAIN_DOC as SKILL_MAIN_DOC5, VERSION as VERSION3 } from "@siming-org/core";
6934
7967
 
6935
7968
  // src/commands/install/writer.ts
6936
- 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";
6937
7970
  import { dirname as dirname4, join as join8 } from "path";
6938
7971
  import { homedir as homedir4 } from "os";
6939
7972
  import { parse as parseYaml3, stringify as stringifyYaml } from "yaml";
@@ -7005,7 +8038,7 @@ function permissionsToMatrix(permissions) {
7005
8038
  }
7006
8039
  return matrix;
7007
8040
  }
7008
- 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";
7009
8042
  function agentToMarkdown(agent, realModel) {
7010
8043
  const fm = {
7011
8044
  name: agent.name,
@@ -7034,7 +8067,7 @@ function agentTargetPath(dir, agent) {
7034
8067
  return join8(dir, "agents", agent.name, "agent.md");
7035
8068
  }
7036
8069
  function readSimingVersion(path) {
7037
- if (!existsSync7(path)) return null;
8070
+ if (!existsSync8(path)) return null;
7038
8071
  let text;
7039
8072
  try {
7040
8073
  text = readFileSync15(path, "utf-8");
@@ -7059,7 +8092,7 @@ function isPlainObject(v) {
7059
8092
  return typeof v === "object" && v !== null && !Array.isArray(v);
7060
8093
  }
7061
8094
  function readMcpConfig(path) {
7062
- if (!existsSync7(path)) return { state: "absent" };
8095
+ if (!existsSync8(path)) return { state: "absent" };
7063
8096
  let text;
7064
8097
  try {
7065
8098
  text = readFileSync15(path, "utf-8");
@@ -7299,7 +8332,7 @@ function compareSkill(s, dir) {
7299
8332
  if (!isSafeAssetName(s.name)) {
7300
8333
  return { ...base, action: "conflict", reason: `unsafe asset name "${s.name}"` };
7301
8334
  }
7302
- if (!existsSync8(path)) return { ...base, action: "install", reason: "not installed locally" };
8335
+ if (!existsSync9(path)) return { ...base, action: "install", reason: "not installed locally" };
7303
8336
  const marked = readSimingVersion(path);
7304
8337
  if (marked === null) {
7305
8338
  return { ...base, localVersion: "unknown", action: "conflict", reason: "no siming-version marker (third-party asset; use --force to overwrite)" };
@@ -7317,7 +8350,7 @@ function compareAgent(a, dir) {
7317
8350
  if (!isSafeAssetName(a.name)) {
7318
8351
  return { ...base, action: "conflict", reason: `unsafe asset name "${a.name}"` };
7319
8352
  }
7320
- if (!existsSync8(path)) return { ...base, action: "install", reason: "not installed locally" };
8353
+ if (!existsSync9(path)) return { ...base, action: "install", reason: "not installed locally" };
7321
8354
  const marked = readSimingVersion(path);
7322
8355
  if (marked === null) {
7323
8356
  return { ...base, localVersion: "unknown", action: "conflict", reason: "no siming-version marker (third-party asset; use --force to overwrite)" };
@@ -7410,7 +8443,7 @@ async function applyInstall(client, scope, projectId, items, opts) {
7410
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" });
7411
8444
  continue;
7412
8445
  }
7413
- const targetExists = existsSync8(targetPath);
8446
+ const targetExists = existsSync9(targetPath);
7414
8447
  writeAssetDirectory(dirname5(targetPath), [
7415
8448
  { relPath: SKILL_MAIN_DOC5, content: skillToMarkdown(r.data) },
7416
8449
  ...(r.data.references ?? []).map((ref) => ({ relPath: ref.path, content: ref.content }))
@@ -7446,7 +8479,7 @@ async function applyInstall(client, scope, projectId, items, opts) {
7446
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" });
7447
8480
  continue;
7448
8481
  }
7449
- const targetExists = existsSync8(targetPath);
8482
+ const targetExists = existsSync9(targetPath);
7450
8483
  writeAssetDirectory(dirname5(targetPath), [
7451
8484
  { relPath: AGENT_MAIN_DOC5, content: agentToMarkdown(r.data, realModel) },
7452
8485
  ...(r.data.references ?? []).map((ref) => ({ relPath: ref.path, content: ref.content }))
@@ -7563,7 +8596,7 @@ var PHASE2_NEXT_STEPS = `\u8D44\u4EA7\u5DF2\u5C31\u7EEA\uFF08\u5168\u5C40\u8D44\
7563
8596
  \uFF08siming task create --template <\u6A21\u677Fcode> --track <\u8F68\u9053> --project <\u9879\u76EEkey> --title "<\u6807\u9898>"\uFF09
7564
8597
  \u5E76\u8BFB\u53D6\u9996\u8282\u70B9\u4E0A\u4E0B\u6587\uFF1Asiming task context <taskId>`;
7565
8598
  var MCP_TOOLING_TEXT = `\u63A5\u53E3\u5206\u5DE5\u73B0\u72B6\uFF08\u6743\u5A01\uFF09\uFF1A
7566
- - 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
7567
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
7568
8601
 
7569
8602
  \u5BBF\u4E3B\u517C\u5BB9\u6027\u81EA\u68C0\u4E0E\u9002\u914D\uFF08\u63A5\u5165\u540E\u5FC5\u505A\uFF09\uFF1A
@@ -8005,7 +9038,7 @@ async function handleUpdateEnums(client, category, body) {
8005
9038
  // src/commands/settings/command.ts
8006
9039
  function createSettingsCommand() {
8007
9040
  const settings = new Command16("settings").description("Settings (enum registries)");
8008
- withCommonOpts(settings.command("enums [category]"), "fancy").description("Show enum registry (all 7 categories, or one category)").action(async (category, opts) => {
9041
+ withCommonOpts(settings.command("enums [category]"), "fancy").description("Show enum registry (all categories, or one category)").action(async (category, opts) => {
8009
9042
  const client = createClient(opts.url);
8010
9043
  const catR = category ? EnumRegistryCategorySchema.safeParse(category) : null;
8011
9044
  if (category && !catR?.success) {
@@ -8059,7 +9092,7 @@ import { VERSION as VERSION4, SIMING_CONFIG_KEYS as SIMING_CONFIG_KEYS2 } from "
8059
9092
 
8060
9093
  // src/commands/server/daemon.ts
8061
9094
  import { execFileSync } from "child_process";
8062
- 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";
8063
9096
  import { join as join10, resolve as resolve2 } from "path";
8064
9097
  import { spawn } from "child_process";
8065
9098
 
@@ -8270,7 +9303,7 @@ function readLogTail(lines = 30) {
8270
9303
  }
8271
9304
  function spawnDaemon(configEnv) {
8272
9305
  const entry = resolve2(process.argv[1] ?? "");
8273
- if (entry.length === 0 || !existsSync9(entry)) {
9306
+ if (entry.length === 0 || !existsSync10(entry)) {
8274
9307
  throw new Error(`\u65E0\u6CD5\u5B9A\u4F4D siming \u5165\u53E3\u811A\u672C\uFF08process.argv[1]=${process.argv[1] ?? "(\u7A7A)"}\uFF09`);
8275
9308
  }
8276
9309
  if (entry.endsWith(".ts")) {