@gobing-ai/spur 0.4.2 → 0.4.3
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/.claude-plugin/marketplace.json +1 -1
- package/config/decisions/maker-liveness.yaml +39 -0
- package/config/plugin-scripts.json +0 -4
- package/config/rules/README.md +1 -0
- package/config/rules/docs/sourced-api-claims.yaml +56 -12
- package/config/rules/strict/runtime-boundaries.yaml +7 -0
- package/config/script-placement-baseline.json +1 -19
- package/config/workflows/feature-lifecycle.yaml +46 -1
- package/config/workflows/feature-verification.yaml +11 -0
- package/package.json +9 -9
- package/plugins/sp/README.md +22 -16
- package/plugins/sp/agents/super-reviewer.md +43 -0
- package/plugins/sp/bunfig.toml +8 -5
- package/plugins/sp/hooks/careful-guard.ts +1 -1
- package/plugins/sp/hooks/context-post-tool.ts +7 -427
- package/plugins/sp/hooks/context-session-start.ts +5 -213
- package/plugins/sp/hooks/context-session-stop.ts +5 -191
- package/plugins/sp/hooks/pi/guard-extension.ts +6 -5
- package/plugins/sp/hooks/task-write-guard.ts +1 -1
- package/plugins/sp/{hooks → lib}/agent-hint.ts +1 -1
- package/plugins/sp/lib/context-post-tool.ts +432 -0
- package/plugins/sp/lib/context-session-start.ts +214 -0
- package/plugins/sp/lib/context-session-stop.ts +196 -0
- package/plugins/sp/lib/dogfood-pipeline.ts +265 -0
- package/plugins/sp/lib/dogfood-report.ts +278 -0
- package/plugins/sp/lib/idea-handoff.generated.mjs +3 -3
- package/plugins/sp/lib/inline-run-app.ts +40 -0
- package/plugins/sp/lib/inline-run.generated.d.mts +3 -3
- package/plugins/sp/lib/inline-run.generated.mjs +1347 -90
- package/plugins/sp/lib/plan-projection.generated.d.mts +150 -0
- package/plugins/sp/lib/plan-projection.generated.mjs +591 -1
- package/plugins/sp/lib/quality-gate.generated.mjs +38 -25
- package/plugins/sp/lib/residual-scan.generated.d.mts +7 -1
- package/plugins/sp/lib/residual-scan.generated.mjs +71 -6
- package/plugins/sp/lib/run-summary-core.ts +34 -370
- package/plugins/sp/lib/transcript.ts +37 -228
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/batch-plan.mjs +586 -0
- package/plugins/sp/scripts/batch-preflight.mjs +23 -1
- package/plugins/sp/scripts/batch-preflight.ts +35 -295
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +108 -87
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +111 -104
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +2 -1
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +7 -265
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +3 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +6 -272
- package/plugins/sp/scripts/feature-verification-steps.mjs +7 -123
- package/plugins/sp/scripts/feature-verification-steps.ts +29 -181
- package/plugins/sp/scripts/inline-run-dispatch.mjs +27 -26
- package/plugins/sp/scripts/inline-run-dispatch.ts +1 -21
- package/plugins/sp/scripts/inline-run-setup.mjs +149 -122
- package/plugins/sp/scripts/inline-run-setup.ts +1 -21
- package/plugins/sp/scripts/quality-gate.mjs +14 -2
- package/plugins/sp/scripts/residual-scan.mjs +73 -6
- package/plugins/sp/scripts/residual-scan.ts +8 -5
- package/plugins/sp/scripts/run-summary.mjs +151 -97
- package/plugins/sp/scripts/run-summary.ts +7 -5
- package/plugins/sp/scripts/session-timeline.mjs +46 -19
- package/plugins/sp/scripts/task-diffstat.mjs +11 -1
- package/plugins/sp/scripts/task-diffstat.ts +8 -39
- package/plugins/sp/skills/code-implementation/SKILL.md +7 -1
- package/plugins/sp/skills/code-verification/references/secu-review.md +2 -1
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +4 -0
- package/plugins/sp/skills/dogfood-testing/SKILL.md +2 -2
- package/plugins/sp/skills/next-feature/SKILL.md +1 -1
- package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
- package/plugins/sp/skills/source-driven-development/SKILL.md +7 -4
- package/plugins/sp/skills/spur-cli/references/builder.md +3 -2
- package/plugins/sp/skills/spur-cli/references/decision.md +32 -7
- package/plugins/sp/skills/spur-cli/references/features.md +46 -5
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +1 -1
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +13 -3
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +22 -0
- package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +14 -0
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +29 -12
- package/spur.js +80681 -79296
- package/web/_astro/{BoardApp.B3GQZvj8.js → BoardApp.5IIl2O-z.js} +1 -1
- package/web/_astro/{BoardApp.BT7L2kSb.js → BoardApp.Drnx-KeM.js} +150 -150
- package/web/_astro/{TaskDetail.Cxov1_N_.js → TaskDetail.Ci4LNn9S.js} +1 -1
- package/web/_astro/{arc.vaT7tPOB.js → arc.j2EVkYVd.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.DRMmTjkN.js → architectureDiagram-3BPJPVTR.DZWSFqEG.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.cla0LO_q.js → blockDiagram-GPEHLZMM.DE6GpXCN.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.D4YAIBKn.js → c4Diagram-AAUBKEIU.DoVR1G6B.js} +1 -1
- package/web/_astro/channel.DmdDxGsq.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.BrSJbMRZ.js → chunk-2J33WTMH.Do6-Gbyf.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.CuLMgKF5.js → chunk-4BX2VUAB.BYV1HGHx.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.zkvgu54F.js → chunk-55IACEB6.CvI1zkrc.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.DxWkACh8.js → chunk-727SXJPM.DNGSCRlX.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.42XrmIlH.js → chunk-AQP2D5EJ.DXhhgT5z.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.Bao2wMK9.js → chunk-FMBD7UC4.dzkRKP2u.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.BhydojD1.js → chunk-ND2GUHAM.DujGLeEf.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.CmZ82g-H.js → chunk-QZHKN3VN.BWtIT3ZU.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.Blr83Gt_.js → classDiagram-4FO5ZUOK.DyApg0Nn.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.Blr83Gt_.js → classDiagram-v2-Q7XG4LA2.DyApg0Nn.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.qmfv0SPn.js → cose-bilkent-S5V4N54A.DJQB-u_l.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.BtELhXPR.js → cynefin-OW5HDTMX.bi3D_siV.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.AWmaAvXo.js → dagre-BM42HDAG.C-f7TrRg.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.DdH34KDi.js → diagram-2AECGRRQ.DrPfjHQB.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.BmNPix1k.js → diagram-5GNKFQAL.C9S4WUHO.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.BNQd_Ppo.js → diagram-KO2AKTUF.Dzzye8QH.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BAPgDi0e.js → diagram-LMA3HP47.DX-LRiof.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.DZ4zyLdC.js → diagram-OG6HWLK6.Z-kAKwIz.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.Q1TySMmw.js → erDiagram-TEJ5UH35.D6dyClSv.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.BXhFBicG.js → flowDiagram-I6XJVG4X.Cx3q2cxD.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.vHSgcuo2.js → ganttDiagram-6RSMTGT7.DU3gH6CT.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.Bi1f73C7.js → gitGraphDiagram-PVQCEYII.ISmCnJ87.js} +1 -1
- package/web/_astro/index.Do2lLdgo.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.DPvXZEWm.js → infoDiagram-5YYISTIA.DR6_iNOK.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.Cz829fsV.js → ishikawaDiagram-YF4QCWOH.D7wY-ZGA.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.enRnB9Me.js → journeyDiagram-JHISSGLW.DkP9-WK8.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.DnTNk6d2.js → kanban-definition-UN3LZRKU.wtgY98M2.js} +1 -1
- package/web/_astro/{linear.BzGnBwNL.js → linear.CI6djNi7.js} +1 -1
- package/web/_astro/{mermaid.core.CSbTpIVz.js → mermaid.core.CuQPxS8b.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.DGJDy8zT.js → mindmap-definition-RKZ34NQL.5HNIEG7R.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.HSPdi5_8.js → pieDiagram-4H26LBE5.CchhDFjE.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.6Gv8mz2Y.js → quadrantDiagram-W4KKPZXB.PYXXJPSJ.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.CnBCtFwa.js → requirementDiagram-4Y6WPE33.cUQuNAKU.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.5M8VWAu8.js → sankeyDiagram-5OEKKPKP.BO7MnRIA.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.DN41XN-U.js → sequenceDiagram-3UESZ5HK.D9bElyD6.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.ErOO3W8j.js → stateDiagram-AJRCARHV.DhGdRz7I.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.B_ZHICM-.js → stateDiagram-v2-BHNVJYJU.5GvLeAoL.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.CWdosr07.js → timeline-definition-PNZ67QCA.LtAT9haM.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.BHzYjNV5.js → vennDiagram-CIIHVFJN.DRth1yLA.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.h8x4IwPX.js → wardleyDiagram-YWT4CUSO.h9MlZfcr.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.RTapW2X8.js → xychartDiagram-2RQKCTM6.DHqy9DYA.js} +1 -1
- package/web/index.html +2 -2
- package/config/proportional-route-table.ts +0 -155
- package/plugins/sp/scripts/daily-summary/logger.ts +0 -28
- package/web/_astro/channel.D4nG9YLf.js +0 -1
- package/web/_astro/index.BhSwN8yY.css +0 -1
- /package/plugins/sp/{hooks → lib}/context-owner.ts +0 -0
- /package/plugins/sp/{hooks → lib}/destructive-policy.ts +0 -0
- /package/plugins/sp/{hooks → lib}/task-file-policy.ts +0 -0
|
@@ -1,303 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* batch-preflight —
|
|
2
|
+
* batch-preflight — planner adapter over the pure admission policy (task 0279; task 1176 move).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* The decisions — TABLE A STOP evaluation, quick command-aware readiness and feature-finding
|
|
5
|
+
* classification — are application policy in `packages/app/src/services/planning-admission.ts`,
|
|
6
|
+
* reached through the generated pure bundle. This entry keeps what belongs to the planner: the CLI
|
|
7
|
+
* surface, its argv/usage/error contracts, and the one-shot recovery hints the batch driver prints.
|
|
8
8
|
*
|
|
9
|
-
* Recovery hints (one-shot after FAIL) map stuck statuses to a single /sp:dev-* hop
|
|
10
|
-
*
|
|
9
|
+
* Recovery hints (one-shot after FAIL) map stuck statuses to a single /sp:dev-* hop per TABLE A; the
|
|
10
|
+
* batch driver prints or dispatches at most once per WBS.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
severity: string;
|
|
42
|
-
message: string;
|
|
43
|
-
section?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface FeaturePreflightClassification {
|
|
47
|
-
aborting: FeaturePreflightFinding[];
|
|
48
|
-
nonAborting: FeaturePreflightFinding[];
|
|
49
|
-
shouldAbort: boolean;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function classifyFeaturePreflightFindings(findings: FeaturePreflightFinding[]): FeaturePreflightClassification {
|
|
53
|
-
const aborting: FeaturePreflightFinding[] = [];
|
|
54
|
-
const nonAborting: FeaturePreflightFinding[] = [];
|
|
55
|
-
for (const f of findings) {
|
|
56
|
-
if (f.severity === 'error') {
|
|
57
|
-
if (NON_ABORTING_PREFLIGHT_CODES.has(f.code)) {
|
|
58
|
-
nonAborting.push(f);
|
|
59
|
-
} else {
|
|
60
|
-
aborting.push(f);
|
|
61
|
-
}
|
|
62
|
-
} else {
|
|
63
|
-
nonAborting.push(f);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
aborting,
|
|
68
|
-
nonAborting,
|
|
69
|
-
shouldAbort: aborting.length > 0,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ── Command-aware quick readiness (task 0814 R2) ──────────────────────────────
|
|
74
|
-
// Read-only admission decision for the requested dev operation. Distinguishes
|
|
75
|
-
// runnable / needs-refinement / blocked / skipped / invalid outcomes without
|
|
76
|
-
// LLM dispatch, full tests/lint, live-data probes, feature mutation, or
|
|
77
|
-
// corpus-wide relational checking. Refinement gaps are work to do, not errors.
|
|
78
|
-
|
|
79
|
-
export type ReadinessOperation = 'run' | 'refine' | 'verify';
|
|
80
|
-
|
|
81
|
-
export interface QuickReadinessInput {
|
|
82
|
-
wbs: string;
|
|
83
|
-
status: TaskStatus;
|
|
84
|
-
operation: ReadinessOperation;
|
|
85
|
-
/** Frontmatter dependencies[] WBS list (run only). */
|
|
86
|
-
dependencies?: string[];
|
|
87
|
-
/** Status of each dependency WBS; missing → treated as unmet (run only). */
|
|
88
|
-
depStatuses?: Record<string, string>;
|
|
89
|
-
/** Size of the status-filtered candidate set after the selector resolved; 0 = empty set. */
|
|
90
|
-
filteredCount?: number;
|
|
91
|
-
/** Required planning sections for this variant+status; empty = not applicable. */
|
|
92
|
-
requiredSections?: string[];
|
|
93
|
-
/** Sections that are actually present (non-placeholder) in the task. */
|
|
94
|
-
presentSections?: string[];
|
|
95
|
-
/** L1/L2/L3 content-policy findings keyed by section; empty = clean. */
|
|
96
|
-
sectionFindings?: Record<string, string>;
|
|
97
|
-
/** Verify only: re-verification semantics (--force) — never a dirty-tree bypass. */
|
|
98
|
-
force?: boolean;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export type QuickReadinessResult =
|
|
102
|
-
| { action: 'runnable'; code: string; reason: string }
|
|
103
|
-
| { action: 'needs-refinement'; code: string; reason: string; gaps: string[] }
|
|
104
|
-
| { action: 'blocked'; code: string; reason: string; unmetDeps?: string[] }
|
|
105
|
-
| { action: 'skipped'; code: string; reason: string }
|
|
106
|
-
| { action: 'invalid'; code: string; reason: string };
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Evaluate quick readiness for a requested dev operation (0814 R2). Read-only:
|
|
110
|
-
* no model, no full tests/lint, no live-data probe, no feature mutation, no
|
|
111
|
-
* corpus-wide relational check. An empty status-filtered set is `skipped`
|
|
112
|
-
* (mirrors the zero-task rule), never an error. Refinement gaps under `refine`
|
|
113
|
-
* are work to do, so they do not block; under `run` they are `needs-refinement`.
|
|
114
|
-
*/
|
|
115
|
-
export function quickReadiness(input: QuickReadinessInput): QuickReadinessResult {
|
|
116
|
-
const status = (input.status ?? '').toLowerCase();
|
|
117
|
-
const operation = input.operation;
|
|
118
|
-
|
|
119
|
-
if (operation !== 'run' && operation !== 'refine' && operation !== 'verify') {
|
|
120
|
-
return {
|
|
121
|
-
action: 'invalid',
|
|
122
|
-
code: 'IV',
|
|
123
|
-
reason: `quick-readiness: unknown operation '${operation}' (${input.wbs})`,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (input.filteredCount !== undefined) {
|
|
128
|
-
if (input.filteredCount < 0) {
|
|
129
|
-
return {
|
|
130
|
-
action: 'invalid',
|
|
131
|
-
code: 'IV',
|
|
132
|
-
reason: `quick-readiness: invalid negative filtered count '${input.filteredCount}' (${input.wbs})`,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
if (input.filteredCount === 0) {
|
|
136
|
-
return {
|
|
137
|
-
action: 'skipped',
|
|
138
|
-
code: 'EMPTY',
|
|
139
|
-
reason: `quick-readiness: empty status-filtered set — nothing to ${operation} (${input.wbs})`,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (status === 'cancelled' || status === 'done') {
|
|
145
|
-
// verify --force re-verification (R2 AC): an already-verified terminal task
|
|
146
|
-
// is re-checked, not skipped — but force never bypasses a dirty tree or the
|
|
147
|
-
// owning gates; it only re-admits a terminal task for re-verification.
|
|
148
|
-
if (operation === 'verify' && input.force === true) {
|
|
149
|
-
return {
|
|
150
|
-
action: 'runnable',
|
|
151
|
-
code: 'FORCE',
|
|
152
|
-
reason: `quick-readiness: verify --force re-verification of ${status} task ${input.wbs}`,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
return {
|
|
156
|
-
action: 'skipped',
|
|
157
|
-
code: status === 'done' ? 'DONE' : 'CANCELLED',
|
|
158
|
-
reason: `quick-readiness: already ${status} — no ${operation} hop (${input.wbs})`,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (status === 'blocked') {
|
|
163
|
-
return {
|
|
164
|
-
action: 'blocked',
|
|
165
|
-
code: 'BLK',
|
|
166
|
-
reason: `quick-readiness: blocked — human/handover first (${input.wbs})`,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Unmet out-of-set dependency is a block for the operation that needs it.
|
|
171
|
-
if (operation === 'run' && input.dependencies && input.dependencies.length > 0) {
|
|
172
|
-
const unmet = input.dependencies.filter((d) => (input.depStatuses?.[d] ?? 'missing').toLowerCase() !== 'done');
|
|
173
|
-
if (unmet.length > 0) {
|
|
174
|
-
return {
|
|
175
|
-
action: 'blocked',
|
|
176
|
-
code: 'DEP',
|
|
177
|
-
reason: `quick-readiness: unmet deps — ${unmet.join(', ')} (${input.wbs})`,
|
|
178
|
-
unmetDeps: unmet,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const required = input.requiredSections ?? [];
|
|
184
|
-
const present = input.presentSections ?? [];
|
|
185
|
-
// A required section is a gap when it is absent from the present-set OR carries a
|
|
186
|
-
// content-policy finding (the caller-supplied `sectionFindings` from the matrix /
|
|
187
|
-
// `TaskCheckService.checkContentPolicy`). This lets the function detect a gap itself
|
|
188
|
-
// rather than depending on the caller to pre-enumerate every missing section.
|
|
189
|
-
const gaps = required.filter((s) => {
|
|
190
|
-
const finding = input.sectionFindings?.[s];
|
|
191
|
-
return !present.includes(s) || (finding !== undefined && finding !== '');
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// refine: missing/incomplete planning sections are the work, not a failure.
|
|
195
|
-
if (operation === 'refine') {
|
|
196
|
-
if (status !== 'backlog' && status !== 'todo') {
|
|
197
|
-
return {
|
|
198
|
-
action: 'skipped',
|
|
199
|
-
code: 'NONPLAN',
|
|
200
|
-
reason: `quick-readiness: refine targets backlog/todo only, not '${status}' (${input.wbs})`,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
return {
|
|
204
|
-
action: 'runnable',
|
|
205
|
-
code: 'OK',
|
|
206
|
-
reason: `quick-readiness: refine ready for ${input.wbs} (${gaps.length} planning gap(s) to fill)`,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (operation === 'verify') {
|
|
211
|
-
if (status !== 'testing' && status !== 'wip') {
|
|
212
|
-
return {
|
|
213
|
-
action: 'invalid',
|
|
214
|
-
code: 'NOVERIFY',
|
|
215
|
-
reason: `quick-readiness: verify needs testing/wip, not '${status}' (${input.wbs})`,
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
return {
|
|
219
|
-
action: 'runnable',
|
|
220
|
-
code: 'OK',
|
|
221
|
-
reason: `quick-readiness: verify ready for ${input.wbs}`,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// run: implementation admission. Eligible statuses are todo/wip/testing;
|
|
226
|
-
// a backlog task needs the chain's auto-promotion first (step 0).
|
|
227
|
-
if (status !== 'todo' && status !== 'wip' && status !== 'testing') {
|
|
228
|
-
return {
|
|
229
|
-
action: 'invalid',
|
|
230
|
-
code: 'NORUN',
|
|
231
|
-
reason: `quick-readiness: run needs todo/wip/testing, not '${status}' (${input.wbs})`,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
if (gaps.length > 0) {
|
|
235
|
-
return {
|
|
236
|
-
action: 'needs-refinement',
|
|
237
|
-
code: 'REFINE',
|
|
238
|
-
reason: `quick-readiness: implementation sections incomplete (${input.wbs})`,
|
|
239
|
-
gaps,
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
return {
|
|
243
|
-
action: 'runnable',
|
|
244
|
-
code: 'OK',
|
|
245
|
-
reason: `quick-readiness: run ready for ${input.wbs}`,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Evaluate whether the batch should launch task-pipeline.yaml for this WBS.
|
|
251
|
-
* STOP codes align with routing-table TABLE A row ids (A2, A7, A8, A9).
|
|
252
|
-
*/
|
|
253
|
-
export function preflightTask(input: PreflightInput): PreflightResult {
|
|
254
|
-
const status = (input.status ?? '').toLowerCase();
|
|
255
|
-
const deps = input.dependencies ?? [];
|
|
256
|
-
const depStatuses = input.depStatuses ?? {};
|
|
257
|
-
|
|
258
|
-
if (status === 'cancelled') {
|
|
259
|
-
return {
|
|
260
|
-
action: 'skip',
|
|
261
|
-
code: 'A9',
|
|
262
|
-
reason: `dev-next: cancelled — nothing to advance (${input.wbs})`,
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (status === 'done') {
|
|
267
|
-
return {
|
|
268
|
-
action: 'skip',
|
|
269
|
-
code: 'A8',
|
|
270
|
-
reason: `dev-next: already done — batch does not auto-wrap (${input.wbs})`,
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (status === 'blocked') {
|
|
275
|
-
return {
|
|
276
|
-
action: 'skip',
|
|
277
|
-
code: 'A7',
|
|
278
|
-
reason: `dev-next: blocked — do not launch pipeline; human/handover first (${input.wbs})`,
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// A2: todo/backlog with any dependency not done (mirrors TABLE A2 / ready filter).
|
|
283
|
-
if (status === 'todo' || status === 'backlog') {
|
|
284
|
-
const unmet = deps.filter((d) => {
|
|
285
|
-
const st = (depStatuses[d] ?? 'missing').toLowerCase();
|
|
286
|
-
return st !== 'done';
|
|
287
|
-
});
|
|
288
|
-
if (unmet.length > 0) {
|
|
289
|
-
return {
|
|
290
|
-
action: 'skip',
|
|
291
|
-
code: 'A2',
|
|
292
|
-
reason: `dev-next: blocked by deps — unmet: ${unmet.join(', ')} (${input.wbs})`,
|
|
293
|
-
unmetDeps: unmet,
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Ready for pipeline: backlog/todo (deps ok), wip, testing.
|
|
299
|
-
return { action: 'run', code: 'OK', reason: `preflight clear — launch task-pipeline for ${input.wbs}` };
|
|
300
|
-
}
|
|
13
|
+
import {
|
|
14
|
+
classifyFeaturePreflightFindings,
|
|
15
|
+
type FeaturePreflightClassification,
|
|
16
|
+
type FeaturePreflightFinding,
|
|
17
|
+
NON_ABORTING_PREFLIGHT_CODES,
|
|
18
|
+
type PreflightInput,
|
|
19
|
+
type PreflightResult,
|
|
20
|
+
preflightTask,
|
|
21
|
+
type QuickReadinessInput,
|
|
22
|
+
type QuickReadinessResult,
|
|
23
|
+
quickReadiness,
|
|
24
|
+
type ReadinessOperation,
|
|
25
|
+
type TaskStatus,
|
|
26
|
+
} from '../lib/plan-projection.generated.mjs';
|
|
27
|
+
|
|
28
|
+
export type {
|
|
29
|
+
FeaturePreflightClassification,
|
|
30
|
+
FeaturePreflightFinding,
|
|
31
|
+
PreflightInput,
|
|
32
|
+
PreflightResult,
|
|
33
|
+
QuickReadinessInput,
|
|
34
|
+
QuickReadinessResult,
|
|
35
|
+
ReadinessOperation,
|
|
36
|
+
TaskStatus,
|
|
37
|
+
};
|
|
38
|
+
// R1: existing plugin callers keep their import path — the moved names are re-exported, not relocated
|
|
39
|
+
// out from under them.
|
|
40
|
+
export { classifyFeaturePreflightFindings, NON_ABORTING_PREFLIGHT_CODES, preflightTask, quickReadiness };
|
|
301
41
|
|
|
302
42
|
/**
|
|
303
43
|
* One-shot recovery hint after a non-PASS pipeline or stuck status (TABLE A primary hop).
|
|
@@ -5,8 +5,9 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
5
5
|
|
|
6
6
|
// plugins/sp/scripts/daily-summary/daily-summary.ts
|
|
7
7
|
import { spawn, spawnSync } from "child_process";
|
|
8
|
-
import { existsSync, mkdirSync, readlinkSync, writeFileSync } from "fs";
|
|
9
|
-
import { dirname, isAbsolute, join, resolve } from "path";
|
|
8
|
+
import { existsSync as existsSync2, mkdirSync, readlinkSync, writeFileSync } from "fs";
|
|
9
|
+
import { dirname as dirname2, isAbsolute, join as join2, resolve as resolve2 } from "path";
|
|
10
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10
11
|
|
|
11
12
|
// plugins/sp/lib/env.ts
|
|
12
13
|
function getEnvVar(name, fallback) {
|
|
@@ -17,20 +18,54 @@ function getEnvVars() {
|
|
|
17
18
|
return process.env;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
// plugins/sp/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
// plugins/sp/lib/inline-run-app.ts
|
|
22
|
+
import { existsSync } from "node:fs";
|
|
23
|
+
import { dirname, join, resolve } from "node:path";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
25
|
+
function resolveAppEntry(spurBin) {
|
|
26
|
+
const fallback = fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url));
|
|
27
|
+
const candidates = spurBin !== "" ? [spurBin] : [fallback];
|
|
28
|
+
for (const candidate of candidates) {
|
|
29
|
+
const tokens = candidate.split(/\s+/).filter(Boolean);
|
|
30
|
+
const mainModule = [...tokens].reverse().find((t) => t.endsWith(".ts"));
|
|
31
|
+
if (mainModule === undefined || !existsSync(mainModule))
|
|
32
|
+
continue;
|
|
33
|
+
const repoRoot = resolve(dirname(mainModule), "..", "..", "..");
|
|
34
|
+
const appEntry = join(repoRoot, "packages", "app", "src", "index.ts");
|
|
35
|
+
if (existsSync(appEntry))
|
|
36
|
+
return { entry: appEntry, portable: false };
|
|
37
|
+
}
|
|
38
|
+
const entry = fileURLToPath(new URL("../lib/inline-run.generated.mjs", import.meta.url));
|
|
39
|
+
if (!existsSync(entry)) {
|
|
40
|
+
throw new Error("inline application bundle is missing — rebuild/install the sp plugin before running inline");
|
|
41
|
+
}
|
|
42
|
+
return { entry, portable: true };
|
|
43
|
+
}
|
|
44
|
+
async function loadInlineApp(spurBin, resolveAppEntry2, required) {
|
|
45
|
+
const { entry, portable } = resolveAppEntry2(spurBin);
|
|
46
|
+
const app = await import(entry);
|
|
47
|
+
const missing = portable ? required.filter((name) => typeof app[name] !== "function") : [];
|
|
48
|
+
if (missing.length > 0) {
|
|
49
|
+
throw new Error(`inline application bundle ${entry} is older than its scripts — missing ${missing.join(", ")}. Reinstall sp with a superskill release that stages lib/ (commit b42961b), or pass --spur-bin <spur checkout>/apps/cli/src/index.ts.`);
|
|
50
|
+
}
|
|
51
|
+
return { app, portable };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// plugins/sp/scripts/daily-summary/daily-summary.ts
|
|
55
|
+
var consoleEnabled = true;
|
|
56
|
+
function enableLogger(consoleOn) {
|
|
57
|
+
consoleEnabled = consoleOn;
|
|
58
|
+
}
|
|
59
|
+
function logAt(stream, message) {
|
|
60
|
+
if (!consoleEnabled)
|
|
24
61
|
return;
|
|
25
62
|
console[stream](message);
|
|
26
63
|
}
|
|
27
64
|
var logger = {
|
|
28
|
-
info: (message) =>
|
|
29
|
-
warn: (message) =>
|
|
30
|
-
error: (message) =>
|
|
65
|
+
info: (message) => logAt("log", message),
|
|
66
|
+
warn: (message) => logAt("warn", message),
|
|
67
|
+
error: (message) => logAt("error", message)
|
|
31
68
|
};
|
|
32
|
-
|
|
33
|
-
// plugins/sp/scripts/daily-summary/daily-summary.ts
|
|
34
69
|
var DAILY_DIR = "docs/daily";
|
|
35
70
|
var DEFAULT_DATE = "today";
|
|
36
71
|
function parseArgs(argv = process.argv.slice(2)) {
|
|
@@ -108,7 +143,7 @@ function getDateRange(dateStr) {
|
|
|
108
143
|
return { start, end };
|
|
109
144
|
}
|
|
110
145
|
var defaultProcessSpawner = (cmd, args, env) => {
|
|
111
|
-
return new Promise((
|
|
146
|
+
return new Promise((resolve3, reject) => {
|
|
112
147
|
try {
|
|
113
148
|
const proc = spawn(cmd, args, {
|
|
114
149
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -119,7 +154,7 @@ var defaultProcessSpawner = (cmd, args, env) => {
|
|
|
119
154
|
proc.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.from(chunk)));
|
|
120
155
|
proc.stderr.on("data", (chunk) => stderrChunks.push(Buffer.from(chunk)));
|
|
121
156
|
proc.on("close", (exitCode) => {
|
|
122
|
-
|
|
157
|
+
resolve3({
|
|
123
158
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
124
159
|
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
125
160
|
exitCode: exitCode ?? 0
|
|
@@ -158,72 +193,49 @@ async function getCcusageData(date) {
|
|
|
158
193
|
return null;
|
|
159
194
|
}
|
|
160
195
|
}
|
|
196
|
+
function composeHistoryHealth(reading) {
|
|
197
|
+
const loops = reading.loops.map((row) => ({
|
|
198
|
+
toolName: row.toolName,
|
|
199
|
+
argsDigest: row.argsDigest || "repeated execution",
|
|
200
|
+
repeats: row.repeats,
|
|
201
|
+
sessionId: row.sessionId,
|
|
202
|
+
fromSeq: row.firstSeq,
|
|
203
|
+
toSeq: row.lastSeq,
|
|
204
|
+
wastedTokens: row.repeats * 250
|
|
205
|
+
}));
|
|
206
|
+
const remediationProposals = [];
|
|
207
|
+
for (const lp of loops.slice(0, 5)) {
|
|
208
|
+
const cleanTool = lp.toolName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
209
|
+
const key = `repetition:${cleanTool}:${lp.argsDigest.slice(0, 16)}`;
|
|
210
|
+
const title = `Break execution loop in ${lp.toolName} (${lp.repeats} repeats)`;
|
|
211
|
+
const command = `spur task create "<title>" --feature <id> && spur task update <wbs> --section Plan --from-file <path>`;
|
|
212
|
+
remediationProposals.push({ key, title, command });
|
|
213
|
+
}
|
|
214
|
+
if (reading.toolCalls > 0 && reading.errorRatePct > 10) {
|
|
215
|
+
remediationProposals.push({
|
|
216
|
+
key: "reliability:tooling:high-error-rate",
|
|
217
|
+
title: `Investigate high tool error rate (${reading.errorRatePct.toFixed(1)}%)`,
|
|
218
|
+
command: `spur task create "<title>" --feature <id> && spur task update <wbs> --section Plan --from-file <path>`
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
toolCalls: reading.toolCalls,
|
|
223
|
+
toolErrors: reading.toolErrors,
|
|
224
|
+
errorRatePct: reading.errorRatePct,
|
|
225
|
+
loops,
|
|
226
|
+
redundantCalls: loops.reduce((acc, l) => acc + Math.max(0, l.repeats - 1), 0),
|
|
227
|
+
wastedTokens: loops.reduce((acc, l) => acc + l.wastedTokens, 0),
|
|
228
|
+
remediationProposals
|
|
229
|
+
};
|
|
230
|
+
}
|
|
161
231
|
async function getSpurHistoryHealth(date, dbPath = ".spur/spur.db") {
|
|
162
232
|
try {
|
|
163
|
-
const resolvedPath =
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
const loopTable = db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get("history_board_loop_findings");
|
|
171
|
-
let loops = [];
|
|
172
|
-
if (loopTable) {
|
|
173
|
-
const rows = db.query(`SELECT tool_name, args_digest, repeats, session_id, first_seq, last_seq, started_at
|
|
174
|
-
FROM history_board_loop_findings
|
|
175
|
-
WHERE started_at IS NULL OR started_at LIKE ?
|
|
176
|
-
ORDER BY repeats DESC
|
|
177
|
-
LIMIT 20`).all(`${date}%`);
|
|
178
|
-
loops = rows.map((r) => ({
|
|
179
|
-
toolName: r.tool_name,
|
|
180
|
-
argsDigest: r.args_digest || "repeated execution",
|
|
181
|
-
repeats: r.repeats,
|
|
182
|
-
sessionId: r.session_id,
|
|
183
|
-
fromSeq: r.first_seq,
|
|
184
|
-
toSeq: r.last_seq,
|
|
185
|
-
wastedTokens: r.repeats * 250
|
|
186
|
-
}));
|
|
187
|
-
}
|
|
188
|
-
let toolCalls = 0;
|
|
189
|
-
let toolErrors = 0;
|
|
190
|
-
const toolTable = db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get("history_board_tool_5m");
|
|
191
|
-
if (toolTable) {
|
|
192
|
-
const stats = db.query(`SELECT SUM(calls) AS calls, SUM(errors) AS errors
|
|
193
|
-
FROM history_board_tool_5m
|
|
194
|
-
WHERE bucket_start LIKE ?`).get(`${date}%`);
|
|
195
|
-
toolCalls = stats?.calls ?? 0;
|
|
196
|
-
toolErrors = stats?.errors ?? 0;
|
|
197
|
-
}
|
|
198
|
-
const redundantCalls = loops.reduce((acc, l) => acc + Math.max(0, l.repeats - 1), 0);
|
|
199
|
-
const wastedTokens = loops.reduce((acc, l) => acc + l.wastedTokens, 0);
|
|
200
|
-
const errorRatePct = toolCalls > 0 ? toolErrors / toolCalls * 100 : 0;
|
|
201
|
-
const remediationProposals = [];
|
|
202
|
-
for (const lp of loops.slice(0, 5)) {
|
|
203
|
-
const cleanTool = lp.toolName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
204
|
-
const key = `repetition:${cleanTool}:${lp.argsDigest.slice(0, 16)}`;
|
|
205
|
-
const title = `Break execution loop in ${lp.toolName} (${lp.repeats} repeats)`;
|
|
206
|
-
const command = `spur task create "<title>" --feature <id> && spur task update <wbs> --section Plan --from-file <path>`;
|
|
207
|
-
remediationProposals.push({ key, title, command });
|
|
208
|
-
}
|
|
209
|
-
if (toolCalls > 0 && errorRatePct > 10) {
|
|
210
|
-
const key = "reliability:tooling:high-error-rate";
|
|
211
|
-
const title = `Investigate high tool error rate (${errorRatePct.toFixed(1)}%)`;
|
|
212
|
-
const command = `spur task create "<title>" --feature <id> && spur task update <wbs> --section Plan --from-file <path>`;
|
|
213
|
-
remediationProposals.push({ key, title, command });
|
|
214
|
-
}
|
|
215
|
-
return {
|
|
216
|
-
toolCalls,
|
|
217
|
-
toolErrors,
|
|
218
|
-
errorRatePct,
|
|
219
|
-
loops,
|
|
220
|
-
redundantCalls,
|
|
221
|
-
wastedTokens,
|
|
222
|
-
remediationProposals
|
|
223
|
-
};
|
|
224
|
-
} finally {
|
|
225
|
-
db.close();
|
|
226
|
-
}
|
|
233
|
+
const resolvedPath = resolve2(process.cwd(), dbPath);
|
|
234
|
+
const { app } = await loadInlineApp(getEnvVar("SPUR_BIN") ?? "", resolveAppEntry, [
|
|
235
|
+
"readHistoryHealth"
|
|
236
|
+
]);
|
|
237
|
+
const reading = await app.readHistoryHealth({ date, dbPath: resolvedPath });
|
|
238
|
+
return reading === null ? null : composeHistoryHealth(reading);
|
|
227
239
|
} catch {
|
|
228
240
|
return null;
|
|
229
241
|
}
|
|
@@ -320,9 +332,9 @@ Please provide the following (press Enter to skip):
|
|
|
320
332
|
input: process.stdin,
|
|
321
333
|
output: process.stdout
|
|
322
334
|
});
|
|
323
|
-
const question = (prompt) => new Promise((
|
|
335
|
+
const question = (prompt) => new Promise((resolve3) => {
|
|
324
336
|
rl.question(prompt, (answer) => {
|
|
325
|
-
|
|
337
|
+
resolve3(answer.trim());
|
|
326
338
|
});
|
|
327
339
|
});
|
|
328
340
|
const learnings = await question(`1. What did you learn today? (optional)
|
|
@@ -468,26 +480,26 @@ function generateMarkdown(summary) {
|
|
|
468
480
|
`);
|
|
469
481
|
}
|
|
470
482
|
function ensureDir(path) {
|
|
471
|
-
if (!
|
|
483
|
+
if (!existsSync2(path)) {
|
|
472
484
|
mkdirSync(path, { recursive: true });
|
|
473
485
|
}
|
|
474
486
|
}
|
|
475
487
|
function writeSummary(markdown, options) {
|
|
476
488
|
const filename = `summary_${options.date.replace(/-/g, "")}.md`;
|
|
477
|
-
const outputPath = options.outputPath ||
|
|
478
|
-
ensureDir(
|
|
489
|
+
const outputPath = options.outputPath || join2(DAILY_DIR, filename);
|
|
490
|
+
ensureDir(join2(outputPath, ".."));
|
|
479
491
|
writeFileSync(outputPath, markdown, "utf-8");
|
|
480
492
|
return outputPath;
|
|
481
493
|
}
|
|
482
494
|
function resolveHistoryReportPath() {
|
|
483
|
-
const pointer =
|
|
484
|
-
if (!
|
|
495
|
+
const pointer = resolve2(process.cwd(), ".spur", "reports", "history", "latest.json");
|
|
496
|
+
if (!existsSync2(pointer)) {
|
|
485
497
|
return;
|
|
486
498
|
}
|
|
487
499
|
try {
|
|
488
500
|
const target = readlinkSync(pointer);
|
|
489
|
-
const resolved = isAbsolute(target) ? target :
|
|
490
|
-
return
|
|
501
|
+
const resolved = isAbsolute(target) ? target : resolve2(dirname2(pointer), target);
|
|
502
|
+
return existsSync2(resolved) ? resolved : undefined;
|
|
491
503
|
} catch {
|
|
492
504
|
return;
|
|
493
505
|
}
|
|
@@ -540,13 +552,21 @@ async function buildDailySummary(options) {
|
|
|
540
552
|
result.gitActivity = gitActivity;
|
|
541
553
|
}
|
|
542
554
|
const historyHealth = await getSpurHistoryHealth(options.date);
|
|
543
|
-
if (historyHealth
|
|
555
|
+
if (historyHealth) {
|
|
544
556
|
result.historyHealth = historyHealth;
|
|
545
557
|
platforms.push("Spur History");
|
|
546
558
|
}
|
|
547
559
|
return result;
|
|
548
560
|
}
|
|
549
561
|
async function main() {
|
|
562
|
+
if (!process.versions.bun) {
|
|
563
|
+
const child = spawnSync("bun", [fileURLToPath2(import.meta.url), ...process.argv.slice(2)], {
|
|
564
|
+
stdio: "inherit"
|
|
565
|
+
});
|
|
566
|
+
if (!child.error)
|
|
567
|
+
process.exit(child.status ?? 1);
|
|
568
|
+
console.error(`daily-summary: Bun not on PATH (${child.error.message}); history health unavailable`);
|
|
569
|
+
}
|
|
550
570
|
const options = parseArgs();
|
|
551
571
|
logger.info(`Generating daily summary for ${options.date}...`);
|
|
552
572
|
try {
|
|
@@ -602,6 +622,7 @@ export {
|
|
|
602
622
|
getCcusageData,
|
|
603
623
|
generateMarkdown,
|
|
604
624
|
ensureDir,
|
|
625
|
+
enableLogger,
|
|
605
626
|
defaultProcessSpawner,
|
|
606
627
|
buildDailySummary
|
|
607
628
|
};
|