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