@ai-content-space/loopx 0.2.8 → 0.2.10
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/README.md +26 -9
- package/README.zh-CN.md +26 -9
- package/docs/loopx/design/loopx-skill-suite-v1-design.md +12 -0
- package/docs/loopx/plans/2026-06-14-loopx-spec-memory-context-loading.md +948 -0
- package/docs/loopx/plans/2026-06-15-support-lens-skills-migration.md +1153 -0
- package/package.json +6 -1
- package/plugins/loopx/.codex-plugin/plugin.json +1 -1
- package/plugins/loopx/skills/api-designer/SKILL.md +232 -0
- package/plugins/loopx/skills/api-designer/references/error-handling.md +541 -0
- package/plugins/loopx/skills/api-designer/references/openapi.md +824 -0
- package/plugins/loopx/skills/api-designer/references/pagination.md +494 -0
- package/plugins/loopx/skills/api-designer/references/rest-patterns.md +335 -0
- package/plugins/loopx/skills/api-designer/references/versioning.md +391 -0
- package/plugins/loopx/skills/architecture-designer/SKILL.md +117 -0
- package/plugins/loopx/skills/architecture-designer/references/adr-template.md +116 -0
- package/plugins/loopx/skills/architecture-designer/references/architecture-patterns.md +346 -0
- package/plugins/loopx/skills/architecture-designer/references/database-selection.md +102 -0
- package/plugins/loopx/skills/architecture-designer/references/nfr-checklist.md +212 -0
- package/plugins/loopx/skills/architecture-designer/references/system-design.md +313 -0
- package/plugins/loopx/skills/clarify/SKILL.md +12 -1
- package/plugins/loopx/skills/cli-developer/SKILL.md +124 -0
- package/plugins/loopx/skills/cli-developer/references/design-patterns.md +221 -0
- package/plugins/loopx/skills/cli-developer/references/go-cli.md +540 -0
- package/plugins/loopx/skills/cli-developer/references/node-cli.md +383 -0
- package/plugins/loopx/skills/cli-developer/references/python-cli.md +422 -0
- package/plugins/loopx/skills/cli-developer/references/ux-patterns.md +448 -0
- package/plugins/loopx/skills/debug/SKILL.md +1 -1
- package/plugins/loopx/skills/doc-readability/SKILL.md +1 -1
- package/plugins/loopx/skills/exec/SKILL.md +1 -1
- package/plugins/loopx/skills/final-review/SKILL.md +1 -1
- package/plugins/loopx/skills/finish/SKILL.md +1 -1
- package/plugins/loopx/skills/fix-review/SKILL.md +1 -1
- package/plugins/loopx/skills/go-style/SKILL.md +1 -1
- package/plugins/loopx/skills/kratos/SKILL.md +2 -1
- package/plugins/loopx/skills/plan-to-exec/SKILL.md +12 -1
- package/plugins/loopx/skills/refactor-plan/SKILL.md +1 -1
- package/plugins/loopx/skills/requirement-analyzer/SKILL.md +161 -0
- package/plugins/loopx/skills/requirement-analyzer/references/example-reports.md +170 -0
- package/plugins/loopx/skills/requirement-analyzer/references/prd-gap-checklist.md +167 -0
- package/plugins/loopx/skills/requirement-analyzer/references/readiness-rubric.md +70 -0
- package/plugins/loopx/skills/requirement-analyzer/references/report-template.md +83 -0
- package/plugins/loopx/skills/review/SKILL.md +1 -1
- package/plugins/loopx/skills/spec/SKILL.md +12 -1
- package/plugins/loopx/skills/sql-style/SKILL.md +108 -0
- package/plugins/loopx/skills/sql-style/references/database-design.md +402 -0
- package/plugins/loopx/skills/sql-style/references/dialect-differences.md +419 -0
- package/plugins/loopx/skills/sql-style/references/optimization.md +384 -0
- package/plugins/loopx/skills/sql-style/references/query-patterns.md +285 -0
- package/plugins/loopx/skills/sql-style/references/window-functions.md +328 -0
- package/plugins/loopx/skills/subagent-exec/SKILL.md +1 -1
- package/plugins/loopx/skills/tdd/SKILL.md +1 -1
- package/plugins/loopx/skills/verify/SKILL.md +1 -1
- package/scripts/verify-skills.mjs +0 -2
- package/skills/RESOLVER.md +8 -1
- package/skills/api-designer/SKILL.md +232 -0
- package/skills/api-designer/references/error-handling.md +541 -0
- package/skills/api-designer/references/openapi.md +824 -0
- package/skills/api-designer/references/pagination.md +494 -0
- package/skills/api-designer/references/rest-patterns.md +335 -0
- package/skills/api-designer/references/versioning.md +391 -0
- package/skills/architecture-designer/SKILL.md +117 -0
- package/skills/architecture-designer/references/adr-template.md +116 -0
- package/skills/architecture-designer/references/architecture-patterns.md +346 -0
- package/skills/architecture-designer/references/database-selection.md +102 -0
- package/skills/architecture-designer/references/nfr-checklist.md +212 -0
- package/skills/architecture-designer/references/system-design.md +313 -0
- package/skills/clarify/SKILL.md +12 -1
- package/skills/cli-developer/SKILL.md +124 -0
- package/skills/cli-developer/references/design-patterns.md +221 -0
- package/skills/cli-developer/references/go-cli.md +540 -0
- package/skills/cli-developer/references/node-cli.md +383 -0
- package/skills/cli-developer/references/python-cli.md +422 -0
- package/skills/cli-developer/references/ux-patterns.md +448 -0
- package/skills/debug/SKILL.md +1 -1
- package/skills/doc-readability/SKILL.md +1 -1
- package/skills/exec/SKILL.md +1 -1
- package/skills/final-review/SKILL.md +1 -1
- package/skills/finish/SKILL.md +1 -1
- package/skills/fix-review/SKILL.md +1 -1
- package/skills/go-style/SKILL.md +1 -1
- package/skills/kratos/SKILL.md +2 -1
- package/skills/plan-to-exec/SKILL.md +12 -1
- package/skills/refactor-plan/SKILL.md +1 -1
- package/skills/requirement-analyzer/SKILL.md +161 -0
- package/skills/requirement-analyzer/references/example-reports.md +170 -0
- package/skills/requirement-analyzer/references/prd-gap-checklist.md +167 -0
- package/skills/requirement-analyzer/references/readiness-rubric.md +70 -0
- package/skills/requirement-analyzer/references/report-template.md +83 -0
- package/skills/review/SKILL.md +1 -1
- package/skills/spec/SKILL.md +12 -1
- package/skills/sql-style/SKILL.md +108 -0
- package/skills/sql-style/references/database-design.md +402 -0
- package/skills/sql-style/references/dialect-differences.md +419 -0
- package/skills/sql-style/references/optimization.md +384 -0
- package/skills/sql-style/references/query-patterns.md +285 -0
- package/skills/sql-style/references/window-functions.md +328 -0
- package/skills/subagent-exec/SKILL.md +1 -1
- package/skills/tdd/SKILL.md +1 -1
- package/skills/verify/SKILL.md +1 -1
- package/src/cli.mjs +4 -1
- package/src/context-manifest.mjs +51 -1
- package/src/install-discovery.mjs +114 -0
- package/src/loopx-context-artifacts.mjs +114 -0
- package/src/project-discovery.mjs +1 -0
- package/src/workflow.mjs +47 -3
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# Window Functions
|
|
2
|
+
|
|
3
|
+
## Ranking Functions
|
|
4
|
+
|
|
5
|
+
```sql
|
|
6
|
+
-- ROW_NUMBER: Sequential numbering within partition
|
|
7
|
+
SELECT
|
|
8
|
+
customer_id,
|
|
9
|
+
order_date,
|
|
10
|
+
total,
|
|
11
|
+
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) as row_num
|
|
12
|
+
FROM orders;
|
|
13
|
+
|
|
14
|
+
-- Get most recent order per customer
|
|
15
|
+
SELECT *
|
|
16
|
+
FROM (
|
|
17
|
+
SELECT
|
|
18
|
+
customer_id,
|
|
19
|
+
order_id,
|
|
20
|
+
order_date,
|
|
21
|
+
total,
|
|
22
|
+
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) as rn
|
|
23
|
+
FROM orders
|
|
24
|
+
) ranked
|
|
25
|
+
WHERE rn = 1;
|
|
26
|
+
|
|
27
|
+
-- RANK: Same values get same rank, gaps in sequence
|
|
28
|
+
SELECT
|
|
29
|
+
student_id,
|
|
30
|
+
score,
|
|
31
|
+
RANK() OVER (ORDER BY score DESC) as rank,
|
|
32
|
+
DENSE_RANK() OVER (ORDER BY score DESC) as dense_rank,
|
|
33
|
+
ROW_NUMBER() OVER (ORDER BY score DESC) as row_num
|
|
34
|
+
FROM exam_results;
|
|
35
|
+
/*
|
|
36
|
+
score=100: rank=1, dense_rank=1, row_num=1
|
|
37
|
+
score=100: rank=1, dense_rank=1, row_num=2
|
|
38
|
+
score=95: rank=3, dense_rank=2, row_num=3
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
-- NTILE: Divide into N buckets
|
|
42
|
+
SELECT
|
|
43
|
+
customer_id,
|
|
44
|
+
total_spent,
|
|
45
|
+
NTILE(4) OVER (ORDER BY total_spent DESC) as quartile
|
|
46
|
+
FROM customer_lifetime_value;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Aggregate Window Functions
|
|
50
|
+
|
|
51
|
+
```sql
|
|
52
|
+
-- Running totals and cumulative sums
|
|
53
|
+
SELECT
|
|
54
|
+
order_date,
|
|
55
|
+
daily_revenue,
|
|
56
|
+
SUM(daily_revenue) OVER (ORDER BY order_date) as cumulative_revenue,
|
|
57
|
+
AVG(daily_revenue) OVER (
|
|
58
|
+
ORDER BY order_date
|
|
59
|
+
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
|
|
60
|
+
) as rolling_7day_avg
|
|
61
|
+
FROM daily_sales;
|
|
62
|
+
|
|
63
|
+
-- Moving average with RANGE
|
|
64
|
+
SELECT
|
|
65
|
+
sale_date,
|
|
66
|
+
amount,
|
|
67
|
+
AVG(amount) OVER (
|
|
68
|
+
ORDER BY sale_date
|
|
69
|
+
RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW
|
|
70
|
+
) as avg_last_7_days
|
|
71
|
+
FROM sales;
|
|
72
|
+
|
|
73
|
+
-- Partition-specific aggregates
|
|
74
|
+
SELECT
|
|
75
|
+
product_id,
|
|
76
|
+
sale_date,
|
|
77
|
+
quantity,
|
|
78
|
+
SUM(quantity) OVER (PARTITION BY product_id ORDER BY sale_date) as cumulative_qty,
|
|
79
|
+
AVG(quantity) OVER (PARTITION BY product_id) as avg_qty_for_product,
|
|
80
|
+
quantity::FLOAT / SUM(quantity) OVER (PARTITION BY product_id) as pct_of_total
|
|
81
|
+
FROM product_sales;
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## LAG and LEAD Functions
|
|
85
|
+
|
|
86
|
+
```sql
|
|
87
|
+
-- Compare with previous/next row
|
|
88
|
+
SELECT
|
|
89
|
+
order_date,
|
|
90
|
+
total,
|
|
91
|
+
LAG(total) OVER (ORDER BY order_date) as previous_day_total,
|
|
92
|
+
LEAD(total) OVER (ORDER BY order_date) as next_day_total,
|
|
93
|
+
total - LAG(total) OVER (ORDER BY order_date) as day_over_day_change
|
|
94
|
+
FROM daily_orders;
|
|
95
|
+
|
|
96
|
+
-- Find gaps in time series
|
|
97
|
+
SELECT
|
|
98
|
+
event_date,
|
|
99
|
+
LAG(event_date) OVER (ORDER BY event_date) as prev_date,
|
|
100
|
+
event_date - LAG(event_date) OVER (ORDER BY event_date) as days_since_last
|
|
101
|
+
FROM events
|
|
102
|
+
WHERE event_date - LAG(event_date) OVER (ORDER BY event_date) > 7;
|
|
103
|
+
|
|
104
|
+
-- Session analysis with time gaps
|
|
105
|
+
SELECT
|
|
106
|
+
user_id,
|
|
107
|
+
action_time,
|
|
108
|
+
LAG(action_time) OVER (PARTITION BY user_id ORDER BY action_time) as prev_action,
|
|
109
|
+
EXTRACT(EPOCH FROM (
|
|
110
|
+
action_time - LAG(action_time) OVER (PARTITION BY user_id ORDER BY action_time)
|
|
111
|
+
)) / 60 as minutes_since_last_action,
|
|
112
|
+
CASE
|
|
113
|
+
WHEN EXTRACT(EPOCH FROM (
|
|
114
|
+
action_time - LAG(action_time) OVER (PARTITION BY user_id ORDER BY action_time)
|
|
115
|
+
)) / 60 > 30 THEN 1
|
|
116
|
+
ELSE 0
|
|
117
|
+
END as new_session
|
|
118
|
+
FROM user_actions;
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## FIRST_VALUE and LAST_VALUE
|
|
122
|
+
|
|
123
|
+
```sql
|
|
124
|
+
-- Compare each row to first/last in partition
|
|
125
|
+
SELECT
|
|
126
|
+
product_id,
|
|
127
|
+
price_date,
|
|
128
|
+
price,
|
|
129
|
+
FIRST_VALUE(price) OVER (
|
|
130
|
+
PARTITION BY product_id
|
|
131
|
+
ORDER BY price_date
|
|
132
|
+
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
|
|
133
|
+
) as initial_price,
|
|
134
|
+
LAST_VALUE(price) OVER (
|
|
135
|
+
PARTITION BY product_id
|
|
136
|
+
ORDER BY price_date
|
|
137
|
+
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
|
|
138
|
+
) as current_price,
|
|
139
|
+
price - FIRST_VALUE(price) OVER (
|
|
140
|
+
PARTITION BY product_id
|
|
141
|
+
ORDER BY price_date
|
|
142
|
+
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
|
|
143
|
+
) as price_change_from_start
|
|
144
|
+
FROM product_price_history;
|
|
145
|
+
|
|
146
|
+
-- NTH_VALUE: Get specific positioned value
|
|
147
|
+
SELECT
|
|
148
|
+
sale_date,
|
|
149
|
+
amount,
|
|
150
|
+
NTH_VALUE(amount, 2) OVER (
|
|
151
|
+
ORDER BY sale_date
|
|
152
|
+
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
|
|
153
|
+
) as second_day_amount
|
|
154
|
+
FROM daily_sales;
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Frame Specifications
|
|
158
|
+
|
|
159
|
+
```sql
|
|
160
|
+
-- ROWS vs RANGE difference
|
|
161
|
+
SELECT
|
|
162
|
+
order_date,
|
|
163
|
+
amount,
|
|
164
|
+
-- ROWS: Physical row offset
|
|
165
|
+
SUM(amount) OVER (
|
|
166
|
+
ORDER BY order_date
|
|
167
|
+
ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING
|
|
168
|
+
) as sum_5_rows,
|
|
169
|
+
-- RANGE: Logical value range
|
|
170
|
+
SUM(amount) OVER (
|
|
171
|
+
ORDER BY order_date
|
|
172
|
+
RANGE BETWEEN INTERVAL '2 days' PRECEDING AND INTERVAL '2 days' FOLLOWING
|
|
173
|
+
) as sum_5_day_range
|
|
174
|
+
FROM orders;
|
|
175
|
+
|
|
176
|
+
-- Common frame patterns
|
|
177
|
+
SELECT
|
|
178
|
+
sale_date,
|
|
179
|
+
revenue,
|
|
180
|
+
-- All preceding rows
|
|
181
|
+
SUM(revenue) OVER (
|
|
182
|
+
ORDER BY sale_date
|
|
183
|
+
ROWS UNBOUNDED PRECEDING
|
|
184
|
+
) as running_total,
|
|
185
|
+
-- Last 3 rows including current
|
|
186
|
+
AVG(revenue) OVER (
|
|
187
|
+
ORDER BY sale_date
|
|
188
|
+
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
|
|
189
|
+
) as ma_3,
|
|
190
|
+
-- Entire partition
|
|
191
|
+
SUM(revenue) OVER (
|
|
192
|
+
PARTITION BY EXTRACT(YEAR FROM sale_date)
|
|
193
|
+
) as yearly_total,
|
|
194
|
+
-- Centered window
|
|
195
|
+
AVG(revenue) OVER (
|
|
196
|
+
ORDER BY sale_date
|
|
197
|
+
ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING
|
|
198
|
+
) as centered_ma_7
|
|
199
|
+
FROM sales;
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Advanced Analytics
|
|
203
|
+
|
|
204
|
+
```sql
|
|
205
|
+
-- Percentile calculations
|
|
206
|
+
SELECT
|
|
207
|
+
employee_id,
|
|
208
|
+
salary,
|
|
209
|
+
PERCENT_RANK() OVER (ORDER BY salary) as pct_rank,
|
|
210
|
+
CUME_DIST() OVER (ORDER BY salary) as cumulative_dist,
|
|
211
|
+
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) OVER () as median_salary,
|
|
212
|
+
PERCENTILE_DISC(0.9) WITHIN GROUP (ORDER BY salary) OVER () as p90_salary
|
|
213
|
+
FROM employees;
|
|
214
|
+
|
|
215
|
+
-- Cohort retention analysis
|
|
216
|
+
WITH user_cohorts AS (
|
|
217
|
+
SELECT
|
|
218
|
+
user_id,
|
|
219
|
+
DATE_TRUNC('month', signup_date) as cohort_month,
|
|
220
|
+
DATE_TRUNC('month', activity_date) as activity_month
|
|
221
|
+
FROM user_activity
|
|
222
|
+
),
|
|
223
|
+
cohort_sizes AS (
|
|
224
|
+
SELECT
|
|
225
|
+
cohort_month,
|
|
226
|
+
COUNT(DISTINCT user_id) as cohort_size
|
|
227
|
+
FROM user_cohorts
|
|
228
|
+
GROUP BY cohort_month
|
|
229
|
+
)
|
|
230
|
+
SELECT
|
|
231
|
+
uc.cohort_month,
|
|
232
|
+
uc.activity_month,
|
|
233
|
+
EXTRACT(MONTH FROM AGE(uc.activity_month, uc.cohort_month)) as months_since_signup,
|
|
234
|
+
COUNT(DISTINCT uc.user_id) as active_users,
|
|
235
|
+
cs.cohort_size,
|
|
236
|
+
ROUND(100.0 * COUNT(DISTINCT uc.user_id) / cs.cohort_size, 2) as retention_pct
|
|
237
|
+
FROM user_cohorts uc
|
|
238
|
+
JOIN cohort_sizes cs ON uc.cohort_month = cs.cohort_month
|
|
239
|
+
GROUP BY uc.cohort_month, uc.activity_month, cs.cohort_size
|
|
240
|
+
ORDER BY uc.cohort_month, months_since_signup;
|
|
241
|
+
|
|
242
|
+
-- Time-series gap filling
|
|
243
|
+
SELECT
|
|
244
|
+
date_series.date,
|
|
245
|
+
COALESCE(s.revenue, 0) as revenue,
|
|
246
|
+
AVG(s.revenue) OVER (
|
|
247
|
+
ORDER BY date_series.date
|
|
248
|
+
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
|
|
249
|
+
) as ma_7day
|
|
250
|
+
FROM generate_series(
|
|
251
|
+
'2024-01-01'::DATE,
|
|
252
|
+
'2024-12-31'::DATE,
|
|
253
|
+
'1 day'::INTERVAL
|
|
254
|
+
) AS date_series(date)
|
|
255
|
+
LEFT JOIN sales s ON date_series.date = s.sale_date;
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Conditional Aggregation with Windows
|
|
259
|
+
|
|
260
|
+
```sql
|
|
261
|
+
-- Filter within window function
|
|
262
|
+
SELECT
|
|
263
|
+
product_id,
|
|
264
|
+
sale_date,
|
|
265
|
+
quantity,
|
|
266
|
+
SUM(quantity) FILTER (WHERE quantity > 10) OVER (
|
|
267
|
+
PARTITION BY product_id
|
|
268
|
+
ORDER BY sale_date
|
|
269
|
+
) as cumulative_large_orders,
|
|
270
|
+
COUNT(*) FILTER (WHERE quantity > 100) OVER (
|
|
271
|
+
PARTITION BY product_id
|
|
272
|
+
) as total_bulk_orders
|
|
273
|
+
FROM sales;
|
|
274
|
+
|
|
275
|
+
-- Multiple conditions
|
|
276
|
+
SELECT
|
|
277
|
+
customer_id,
|
|
278
|
+
order_date,
|
|
279
|
+
total,
|
|
280
|
+
COUNT(*) FILTER (WHERE total > 1000) OVER (
|
|
281
|
+
PARTITION BY customer_id
|
|
282
|
+
) as high_value_order_count,
|
|
283
|
+
AVG(total) FILTER (WHERE total < 100) OVER (
|
|
284
|
+
PARTITION BY customer_id
|
|
285
|
+
) as avg_small_order_value
|
|
286
|
+
FROM orders;
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Performance Considerations
|
|
290
|
+
|
|
291
|
+
```sql
|
|
292
|
+
-- Avoid multiple window passes - combine into one
|
|
293
|
+
-- Bad: Multiple scans
|
|
294
|
+
SELECT
|
|
295
|
+
product_id,
|
|
296
|
+
(SELECT AVG(price) FROM products) as avg_price,
|
|
297
|
+
(SELECT MAX(price) FROM products) as max_price
|
|
298
|
+
FROM products;
|
|
299
|
+
|
|
300
|
+
-- Good: Single window pass
|
|
301
|
+
SELECT DISTINCT
|
|
302
|
+
AVG(price) OVER () as avg_price,
|
|
303
|
+
MAX(price) OVER () as max_price
|
|
304
|
+
FROM products;
|
|
305
|
+
|
|
306
|
+
-- Materialize expensive windows
|
|
307
|
+
CREATE MATERIALIZED VIEW product_rankings AS
|
|
308
|
+
SELECT
|
|
309
|
+
product_id,
|
|
310
|
+
category,
|
|
311
|
+
sales_count,
|
|
312
|
+
RANK() OVER (PARTITION BY category ORDER BY sales_count DESC) as category_rank,
|
|
313
|
+
PERCENT_RANK() OVER (ORDER BY sales_count DESC) as overall_percentile
|
|
314
|
+
FROM product_sales_summary;
|
|
315
|
+
|
|
316
|
+
CREATE INDEX idx_product_rankings_category ON product_rankings(category, category_rank);
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Common Patterns
|
|
320
|
+
|
|
321
|
+
1. **Top N per Group**: Use ROW_NUMBER() with WHERE rn <= N
|
|
322
|
+
2. **Running Totals**: SUM() OVER (ORDER BY date)
|
|
323
|
+
3. **Moving Averages**: AVG() with ROWS BETWEEN N PRECEDING
|
|
324
|
+
4. **Session Analysis**: LAG() to detect time gaps
|
|
325
|
+
5. **Deduplication**: ROW_NUMBER() OVER (PARTITION BY key ORDER BY priority) WHERE rn = 1
|
|
326
|
+
6. **Percentiles**: PERCENT_RANK() or PERCENTILE_CONT()
|
|
327
|
+
7. **Year-over-Year**: LAG(value, 12) OVER (ORDER BY month)
|
|
328
|
+
8. **Cohort Analysis**: PARTITION BY cohort_date, aggregate over activity periods
|
|
@@ -3,7 +3,7 @@ name: subagent-exec
|
|
|
3
3
|
description: "Executes approved loopx implementation plans with fresh subagents per independent task and staged review. Not for planning, unclear requirements, or tightly coupled edits."
|
|
4
4
|
when_to_use: "approved implementation plan, independent tasks, subagent execution, staged spec review, code quality review, parallel-capable execution"
|
|
5
5
|
metadata:
|
|
6
|
-
version: "0.2.
|
|
6
|
+
version: "0.2.10"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Subagent Exec
|
package/skills/tdd/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: tdd
|
|
|
3
3
|
description: "Guides feature and bugfix implementation through a failing test before production code and red-green-refactor discipline. Not for generated files or throwaway prototypes."
|
|
4
4
|
when_to_use: "tdd, failing test first, feature implementation, bugfix, regression test, red green refactor, 测试先行"
|
|
5
5
|
metadata:
|
|
6
|
-
version: "0.2.
|
|
6
|
+
version: "0.2.10"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Test-Driven Development (TDD)
|
package/skills/verify/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: verify
|
|
|
3
3
|
description: "Requires fresh verification evidence before claiming work is complete, fixed, passing, review-ready, or ready to commit. Not for speculative confidence or stale results."
|
|
4
4
|
when_to_use: "verify, completion claim, fixed claim, tests pass, review-ready, commit, fresh evidence, 验证, 完成前检查"
|
|
5
5
|
metadata:
|
|
6
|
-
version: "0.2.
|
|
6
|
+
version: "0.2.10"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Verification Before Completion
|
package/src/cli.mjs
CHANGED
|
@@ -30,7 +30,7 @@ function usage() {
|
|
|
30
30
|
' loopx status [slug] [--json]',
|
|
31
31
|
' loopx next <slug> [--json]',
|
|
32
32
|
' loopx setup-context',
|
|
33
|
-
' loopx install-skills [--target <codex|claude|all>] [--project] [--mode <copy|symlink>] [--dir <path>] [--yes] [--dry-run] [--json]',
|
|
33
|
+
' loopx install-skills [--target <codex|claude|all>] [--project] [--mode <copy|symlink>] [--dir <path>] [--add-agent-guidance] [--yes] [--dry-run] [--json]',
|
|
34
34
|
' loopx doctor [--json]',
|
|
35
35
|
' loopx migrate',
|
|
36
36
|
' loopx repair-install',
|
|
@@ -60,6 +60,7 @@ async function promptInstallOptions() {
|
|
|
60
60
|
const targetAnswer = (await rl.question('Install targets (codex, claude, all) [all]: ')).trim().toLowerCase();
|
|
61
61
|
const projectAnswer = (await rl.question('Install Claude project skills instead of user skills? [y/N]: ')).trim().toLowerCase();
|
|
62
62
|
const modeAnswer = (await rl.question('Install mode (copy, symlink) [copy]: ')).trim().toLowerCase();
|
|
63
|
+
const guidanceAnswer = (await rl.question('Add loopx guidance to Codex AGENTS.md / Claude CLAUDE.md? [y/N]: ')).trim().toLowerCase();
|
|
63
64
|
const proceedAnswer = (await rl.question('Proceed? [y/N]: ')).trim().toLowerCase();
|
|
64
65
|
if (proceedAnswer !== 'y' && proceedAnswer !== 'yes') {
|
|
65
66
|
return null;
|
|
@@ -69,6 +70,7 @@ async function promptInstallOptions() {
|
|
|
69
70
|
targets: target === 'all' ? ['codex', 'claude'] : [target],
|
|
70
71
|
project: projectAnswer === 'y' || projectAnswer === 'yes',
|
|
71
72
|
installMethod: modeAnswer === 'symlink' ? 'symlink' : 'copy',
|
|
73
|
+
agentGuidance: guidanceAnswer === 'y' || guidanceAnswer === 'yes',
|
|
72
74
|
};
|
|
73
75
|
} finally {
|
|
74
76
|
rl.close();
|
|
@@ -83,6 +85,7 @@ function installOptionsFromArgs(options) {
|
|
|
83
85
|
project: Boolean(options.get('--project')),
|
|
84
86
|
installMethod: options.get('--mode') === 'symlink' ? 'symlink' : 'copy',
|
|
85
87
|
dir: options.get('--dir'),
|
|
88
|
+
agentGuidance: Boolean(options.get('--add-agent-guidance') || options.get('--add-codex-agents-guidance')),
|
|
86
89
|
};
|
|
87
90
|
}
|
|
88
91
|
|
package/src/context-manifest.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
4
4
|
|
|
5
|
+
import { discoverLoopxContextArtifacts } from './loopx-context-artifacts.mjs';
|
|
5
6
|
import { inspectWorkspaceContext, resolveWorkspaceContextPaths } from './workspace-context.mjs';
|
|
6
7
|
|
|
7
8
|
export const CONTEXT_MANIFEST_SCHEMA_VERSION = 1;
|
|
@@ -64,6 +65,53 @@ function stableRows(rows) {
|
|
|
64
65
|
.slice(0, MAX_MANIFEST_ROWS);
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
async function loopxRepoContextRows(cwd, stage, priorityStart) {
|
|
69
|
+
const artifacts = await discoverLoopxContextArtifacts(cwd);
|
|
70
|
+
const rows = [];
|
|
71
|
+
let priority = priorityStart;
|
|
72
|
+
if (artifacts.specsRoot) {
|
|
73
|
+
rows.push(row(cwd, {
|
|
74
|
+
stage,
|
|
75
|
+
kind: 'repo-specs',
|
|
76
|
+
path: artifacts.specsRoot,
|
|
77
|
+
reason: 'long_lived_loopx_specs_directory',
|
|
78
|
+
priority: priority++,
|
|
79
|
+
required: false,
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
for (const spec of artifacts.specFiles) {
|
|
83
|
+
rows.push(row(cwd, {
|
|
84
|
+
stage,
|
|
85
|
+
kind: 'repo-spec',
|
|
86
|
+
path: spec.path,
|
|
87
|
+
reason: 'long_lived_loopx_spec',
|
|
88
|
+
priority: priority++,
|
|
89
|
+
required: false,
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
if (artifacts.memorySummary) {
|
|
93
|
+
rows.push(row(cwd, {
|
|
94
|
+
stage,
|
|
95
|
+
kind: 'memory-summary',
|
|
96
|
+
path: artifacts.memorySummary.path,
|
|
97
|
+
reason: 'curated_loopx_project_memory',
|
|
98
|
+
priority: priority++,
|
|
99
|
+
required: false,
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
if (artifacts.memoryIndex) {
|
|
103
|
+
rows.push(row(cwd, {
|
|
104
|
+
stage,
|
|
105
|
+
kind: 'memory-index',
|
|
106
|
+
path: artifacts.memoryIndex.path,
|
|
107
|
+
reason: 'curated_loopx_memory_retrieval_index',
|
|
108
|
+
priority: priority++,
|
|
109
|
+
required: false,
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
return rows;
|
|
113
|
+
}
|
|
114
|
+
|
|
67
115
|
export async function writeContextManifest(path, rows) {
|
|
68
116
|
const text = stableRows(rows).map((item) => JSON.stringify(item)).join('\n');
|
|
69
117
|
await mkdir(dirname(path), { recursive: true });
|
|
@@ -141,6 +189,7 @@ export async function generateBuildContextManifest({ cwd, root, state, slug }) {
|
|
|
141
189
|
row(cwd, { stage: 'build', kind: 'domain-context', path: contextPaths.domainGlossary, reason: 'domain_vocabulary', priority: 34, required: contextSetup.status !== 'missing' }),
|
|
142
190
|
row(cwd, { stage: 'build', kind: 'agent-domain', path: contextPaths.agentDomain, reason: 'agent_context_rules', priority: 35, required: false }),
|
|
143
191
|
row(cwd, { stage: 'build', kind: 'workspace-config', path: join(cwd, '.loopx', 'config.json'), reason: 'project_rules_spec_sources_and_verification_commands', priority: 36, required: false }),
|
|
192
|
+
...await loopxRepoContextRows(cwd, 'build', 37),
|
|
144
193
|
];
|
|
145
194
|
const manifestPath = buildContextManifestPath(root);
|
|
146
195
|
await writeContextManifest(manifestPath, rows);
|
|
@@ -163,7 +212,8 @@ export async function generateReviewContextManifest({ cwd, root, state, slug })
|
|
|
163
212
|
row(cwd, { stage: 'review', kind: 'build-support', path: join(root, 'build-support'), reason: 'build_gate_evidence', priority: 30, required: false }),
|
|
164
213
|
row(cwd, { stage: 'review', kind: 'agent-domain', path: contextPaths.agentDomain, reason: 'agent_context_rules', priority: 31, required: false }),
|
|
165
214
|
row(cwd, { stage: 'review', kind: 'workspace-config', path: join(cwd, '.loopx', 'config.json'), reason: 'project_rules_spec_sources_and_verification_commands', priority: 32, required: false }),
|
|
166
|
-
|
|
215
|
+
...await loopxRepoContextRows(cwd, 'review', 33),
|
|
216
|
+
row(cwd, { stage: 'review', kind: 'state', path: join(root, 'state.json'), reason: 'workflow_state', priority: 60 }),
|
|
167
217
|
];
|
|
168
218
|
const manifestPath = reviewContextManifestPath(root);
|
|
169
219
|
await writeContextManifest(manifestPath, rows);
|
|
@@ -29,8 +29,13 @@ const LOOPX_SKILLS = [
|
|
|
29
29
|
'tdd',
|
|
30
30
|
'verify',
|
|
31
31
|
'doc-readability',
|
|
32
|
+
'requirement-analyzer',
|
|
32
33
|
'go-style',
|
|
33
34
|
'kratos',
|
|
35
|
+
'api-designer',
|
|
36
|
+
'architecture-designer',
|
|
37
|
+
'sql-style',
|
|
38
|
+
'cli-developer',
|
|
34
39
|
];
|
|
35
40
|
const LOOPX_INSTALLATION_IDENTITY = 'loopx';
|
|
36
41
|
const LOOPX_MANAGED_SCRIPT_ITEMS = [
|
|
@@ -49,6 +54,18 @@ const LOOPX_MANAGED_SCRIPT_ITEMS = [
|
|
|
49
54
|
targetRelativePath: '.claude/hooks/loopx-workflow-hook.mjs',
|
|
50
55
|
},
|
|
51
56
|
];
|
|
57
|
+
const LOOPX_AGENT_GUIDANCE_BLOCK_ID = 'specs-and-memory-context';
|
|
58
|
+
const LOOPX_AGENT_GUIDANCE_HEADING = '## loopx Specs And Memory';
|
|
59
|
+
const LOOPX_AGENT_GUIDANCE_CONTENT = [
|
|
60
|
+
LOOPX_AGENT_GUIDANCE_HEADING,
|
|
61
|
+
'',
|
|
62
|
+
'When working in a repository that uses loopx:',
|
|
63
|
+
'',
|
|
64
|
+
'- If `docs/loopx/specs/` exists, inspect relevant specs before clarify, spec, plan, implementation, or review. Use `docs/loopx/specs/index.md` as a map when present, but do not require it.',
|
|
65
|
+
'- If `.loopx/memory/MEMORY.md` exists, read it as curated project memory.',
|
|
66
|
+
'- If `.loopx/memory/index.jsonl` exists, use it only to find relevant active memory cards.',
|
|
67
|
+
'- Treat current user instructions and named source documents as highest priority, repo specs as binding long-lived rules, and memory as advisory context.',
|
|
68
|
+
].join('\n');
|
|
52
69
|
const LOOPX_GOVERNED_SOURCE_ITEMS = [
|
|
53
70
|
{
|
|
54
71
|
name: 'loopx-plugin-manifest',
|
|
@@ -128,6 +145,19 @@ export function getClaudeSettingsPath(env = process.env) {
|
|
|
128
145
|
return resolve(env.LOOPX_CLAUDE_SETTINGS_PATH || join(home, '.claude', 'settings.json'));
|
|
129
146
|
}
|
|
130
147
|
|
|
148
|
+
export function getCodexAgentsPath(env = process.env) {
|
|
149
|
+
const home = resolve(env.LOOPX_HOME || env.HOME || process.cwd());
|
|
150
|
+
return resolve(env.LOOPX_CODEX_AGENTS_PATH || join(home, '.codex', 'AGENTS.md'));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function getClaudeAgentsPath(env = process.env, options = {}) {
|
|
154
|
+
if (options.project === true) {
|
|
155
|
+
return resolve(env.LOOPX_INSTALL_CWD || process.cwd(), 'CLAUDE.md');
|
|
156
|
+
}
|
|
157
|
+
const home = resolve(env.LOOPX_HOME || env.HOME || process.cwd());
|
|
158
|
+
return resolve(env.LOOPX_CLAUDE_AGENTS_PATH || join(home, '.claude', 'CLAUDE.md'));
|
|
159
|
+
}
|
|
160
|
+
|
|
131
161
|
export function getSkillLockPath(env = process.env) {
|
|
132
162
|
return resolve(env.LOOPX_SKILL_LOCK_PATH || join(getAgentsRoot(env), '.skill-lock.json'));
|
|
133
163
|
}
|
|
@@ -454,6 +484,85 @@ async function removeInstalledFile(path) {
|
|
|
454
484
|
await rm(path, { force: true });
|
|
455
485
|
}
|
|
456
486
|
|
|
487
|
+
function managedBlockMarkers(id) {
|
|
488
|
+
return {
|
|
489
|
+
start: `<!-- loopx:managed:block ${id} -->`,
|
|
490
|
+
end: `<!-- /loopx:managed:block ${id} -->`,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function renderManagedBlock(id, content) {
|
|
495
|
+
const markers = managedBlockMarkers(id);
|
|
496
|
+
return `${markers.start}\n${content.trim()}\n${markers.end}`;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function managedBlockPattern(id) {
|
|
500
|
+
const escaped = String(id).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
501
|
+
return new RegExp(`<!--\\s*loopx:managed:block\\s+${escaped}\\s*-->[\\s\\S]*?<!--\\s*\\/loopx:managed:block\\s+${escaped}\\s*-->`);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function upsertManagedBlock(existing, id, content) {
|
|
505
|
+
const nextBlock = renderManagedBlock(id, content);
|
|
506
|
+
const pattern = managedBlockPattern(id);
|
|
507
|
+
if (pattern.test(existing)) {
|
|
508
|
+
const nextContent = existing.replace(pattern, nextBlock);
|
|
509
|
+
return {
|
|
510
|
+
content: nextContent,
|
|
511
|
+
changed: nextContent !== existing,
|
|
512
|
+
existed: true,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
const trimmed = existing.trimEnd();
|
|
516
|
+
const contentWithBlock = trimmed
|
|
517
|
+
? `${trimmed}\n\n${nextBlock}\n`
|
|
518
|
+
: `${nextBlock}\n`;
|
|
519
|
+
return {
|
|
520
|
+
content: contentWithBlock,
|
|
521
|
+
changed: true,
|
|
522
|
+
existed: false,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export async function installAgentGuidanceFile(path, options = {}) {
|
|
527
|
+
const content = options.content || LOOPX_AGENT_GUIDANCE_CONTENT;
|
|
528
|
+
const id = options.id || LOOPX_AGENT_GUIDANCE_BLOCK_ID;
|
|
529
|
+
const existing = existsSync(path) ? await readFile(path, 'utf8') : '';
|
|
530
|
+
const existed = existsSync(path);
|
|
531
|
+
const next = upsertManagedBlock(existing, id, content);
|
|
532
|
+
if (!next.changed) {
|
|
533
|
+
return { status: 'already-current', path };
|
|
534
|
+
}
|
|
535
|
+
await ensureDir(dirname(path));
|
|
536
|
+
await writeFile(path, `${next.content.replace(/\s+$/, '')}\n`);
|
|
537
|
+
return {
|
|
538
|
+
status: next.existed ? 'updated' : (existed ? 'installed' : 'created'),
|
|
539
|
+
path,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function agentGuidanceEnabled(options = {}) {
|
|
544
|
+
return Boolean(options.agentGuidance || options.codexAgentsGuidance);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
export async function installAgentGuidance(env = process.env, options = {}) {
|
|
548
|
+
const target = options.target || env.LOOPX_INSTALL_TARGET || 'codex';
|
|
549
|
+
const enabled = agentGuidanceEnabled(options);
|
|
550
|
+
const result = {};
|
|
551
|
+
if (target === 'codex' || target === 'all') {
|
|
552
|
+
const path = getCodexAgentsPath(env);
|
|
553
|
+
result.codex = enabled
|
|
554
|
+
? await installAgentGuidanceFile(path)
|
|
555
|
+
: { status: 'recommended', path };
|
|
556
|
+
}
|
|
557
|
+
if (target === 'claude' || target === 'all') {
|
|
558
|
+
const path = getClaudeAgentsPath(env, options);
|
|
559
|
+
result.claude = enabled
|
|
560
|
+
? await installAgentGuidanceFile(path)
|
|
561
|
+
: { status: 'recommended', path };
|
|
562
|
+
}
|
|
563
|
+
return result;
|
|
564
|
+
}
|
|
565
|
+
|
|
457
566
|
async function canonicalTargetOwnership(skillName, env = process.env, options = {}) {
|
|
458
567
|
const targetDir = installedSkillDir(skillName, env);
|
|
459
568
|
const sourceDir = skillSourceDir(skillName, env, options.skillSourceRoot);
|
|
@@ -736,11 +845,16 @@ export async function installBundledSkills(env = process.env, options = {}) {
|
|
|
736
845
|
items: nextTemplateItems,
|
|
737
846
|
});
|
|
738
847
|
const templateGovernance = await inspectTemplateGovernance(baselinePath);
|
|
848
|
+
const agentGuidance = await installAgentGuidance(env, {
|
|
849
|
+
...options,
|
|
850
|
+
target: options.target || env.LOOPX_INSTALL_TARGET || 'codex',
|
|
851
|
+
});
|
|
739
852
|
return {
|
|
740
853
|
ok: conflicts.length === 0,
|
|
741
854
|
installed,
|
|
742
855
|
conflicts,
|
|
743
856
|
skipped,
|
|
857
|
+
agentGuidance,
|
|
744
858
|
templateGovernance,
|
|
745
859
|
inspection: await inspectInstallState(env),
|
|
746
860
|
};
|