@nebutra/idea-plaza 0.1.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/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @nebutra/idea-plaza
2
+
3
+ Status: **WIP**
4
+
5
+ Opt-in idea publishing and fork lineage for Nebutra ecosystem artifacts.
6
+
7
+ ## Boundary
8
+
9
+ Ideas are private by default. This package writes explicit public snapshots and
10
+ fork metadata; it does not expose private founder data or own a global registry
11
+ transport.
12
+
13
+ ## Commands
14
+
15
+ ```bash
16
+ pnpm idea-plaza:doctor
17
+ pnpm idea-plaza:quickstart
18
+ pnpm idea-plaza:debug
19
+ ```
20
+
21
+ ## Current Closure
22
+
23
+ - Sensitive-field scan before publish.
24
+ - Surface/detail/cloneable publication levels.
25
+ - Immutable local snapshot artifacts.
26
+ - Fork attribution always enabled.
27
+ - Cemetery warning metadata on cards.
@@ -0,0 +1,147 @@
1
+ // src/index.ts
2
+ import { mkdir } from "fs/promises";
3
+ import { join } from "path";
4
+ import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
5
+ import { ContentStore } from "@nebutra/content-store";
6
+ import { assertPublicDisclosureSafe } from "@nebutra/ecosystem-safety";
7
+ import { CapabilityError } from "@nebutra/errors";
8
+ import { EventLog } from "@nebutra/event-log";
9
+ import { assetId } from "@nebutra/generation-context";
10
+ import { scanForSensitiveFields } from "@nebutra/ecosystem-safety";
11
+ function requireTenant(value) {
12
+ if (!value?.trim()) {
13
+ throw new CapabilityError("idea-plaza", "Idea Plaza requires tenant context", {
14
+ suggestion: "Pass tenantId so publish snapshots stay tenant-scoped.",
15
+ statusCode: 400
16
+ });
17
+ }
18
+ return value;
19
+ }
20
+ function bodyForScan(request) {
21
+ return [request.title, request.oneLine, request.body ?? "", request.tags.join(" ")].join("\n");
22
+ }
23
+ var IdeaPlaza = class _IdeaPlaza {
24
+ #tenantId;
25
+ #debugRoot;
26
+ #contentStore;
27
+ #eventLog;
28
+ #ideas = /* @__PURE__ */ new Map();
29
+ #forks = /* @__PURE__ */ new Map();
30
+ constructor(options) {
31
+ this.#tenantId = requireTenant(options.tenantId);
32
+ this.#debugRoot = options.debugRoot ?? process.cwd();
33
+ this.#contentStore = options.contentStore;
34
+ this.#eventLog = options.eventLog;
35
+ }
36
+ static async open(root = ".nebutra/idea-plaza", options = {}) {
37
+ const tenantId = options.tenantId ?? "local";
38
+ await mkdir(root, { recursive: true });
39
+ const contentStore = await ContentStore.open(join(root, "content"), { tenantId });
40
+ const eventLog = await EventLog.open(join(root, "event-log"), { tenantId });
41
+ return new _IdeaPlaza({ ...options, tenantId, contentStore, eventLog });
42
+ }
43
+ async publish(request) {
44
+ assertPublicDisclosureSafe({
45
+ capability: "idea-plaza",
46
+ content: bodyForScan(request),
47
+ ...request.redactions !== void 0 ? { redactions: request.redactions } : {},
48
+ suggestion: "Review detected private values and pass redactions before publishing."
49
+ });
50
+ const ideaId = assetId("idea", `${request.title}_${request.oneLine}`);
51
+ const publicationPath = `plaza/ideas/${ideaId}.json`;
52
+ const publishedAt = (/* @__PURE__ */ new Date()).toISOString();
53
+ const idea = {
54
+ ideaId,
55
+ title: request.title,
56
+ oneLine: request.oneLine,
57
+ level: request.level,
58
+ tags: request.tags,
59
+ redactions: request.redactions ?? [],
60
+ cemeteryWarnings: request.cemeteryWarnings ?? [],
61
+ publicationPath,
62
+ publishedAt
63
+ };
64
+ const snapshot = `${JSON.stringify({ ...idea, body: request.body ?? "" }, null, 2)}
65
+ `;
66
+ await this.#contentStore.write(publicationPath, snapshot);
67
+ const eventId = await this.#eventLog.commit({
68
+ traceId: assetId("idea_plaza_publish", ideaId),
69
+ kind: "content_write",
70
+ affected: [publicationPath],
71
+ parent: null,
72
+ snapshot: { [publicationPath]: snapshot }
73
+ });
74
+ const published = { ...idea, eventId };
75
+ this.#ideas.set(ideaId, published);
76
+ await this.#debug({ type: "publish", tenantId: this.#tenantId, ideaId, level: request.level });
77
+ return published;
78
+ }
79
+ async fork(ideaId, request) {
80
+ if (!this.#ideas.has(ideaId)) {
81
+ throw new CapabilityError("idea-plaza", "Idea fork source not found", {
82
+ suggestion: "Fork only from an explicit public or cloneable snapshot in the current feed.",
83
+ metadata: { ideaId },
84
+ statusCode: 404
85
+ });
86
+ }
87
+ const fork = {
88
+ forkId: assetId("idea_fork", `${ideaId}_${request.newFounderId}`),
89
+ sourceIdeaId: ideaId,
90
+ newFounderId: request.newFounderId,
91
+ inheritPlayPreferences: request.inheritPlayPreferences ?? false,
92
+ attribution: {
93
+ displayInNewCompany: true,
94
+ publicLineage: true
95
+ },
96
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
97
+ };
98
+ const path = `plaza/forks/${fork.forkId}.json`;
99
+ const snapshot = `${JSON.stringify(fork, null, 2)}
100
+ `;
101
+ await this.#contentStore.write(path, snapshot);
102
+ await this.#eventLog.commit({
103
+ traceId: assetId("idea_plaza_fork", fork.forkId),
104
+ kind: "content_write",
105
+ affected: [path],
106
+ parent: null,
107
+ snapshot: { [path]: snapshot }
108
+ });
109
+ this.#forks.set(fork.forkId, fork);
110
+ await this.#debug({ type: "fork", tenantId: this.#tenantId, ideaId, forkId: fork.forkId });
111
+ return fork;
112
+ }
113
+ async feed(request) {
114
+ const items = [...this.#ideas.values()].sort(
115
+ (left, right) => request.sort === "new" ? right.publishedAt.localeCompare(left.publishedAt) : right.cemeteryWarnings.length - left.cemeteryWarnings.length || right.publishedAt.localeCompare(left.publishedAt)
116
+ ).slice(0, request.limit);
117
+ await this.#debug({ type: "feed", tenantId: this.#tenantId, sort: request.sort });
118
+ return { items };
119
+ }
120
+ async doctor() {
121
+ const report = {
122
+ capability: "idea-plaza",
123
+ ok: true,
124
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
125
+ features: ["publish", "redact", "fork-lineage", "feed", "cemetery-warning"],
126
+ suggestion: "Local snapshots are active; public registry transport and moderation queues remain adapter handoffs."
127
+ };
128
+ await this.#debug({ type: "doctor", tenantId: this.#tenantId, ideas: this.#ideas.size });
129
+ return report;
130
+ }
131
+ async close() {
132
+ await this.#contentStore.close();
133
+ }
134
+ async #debug(entry) {
135
+ await appendCapabilityDebug("idea-plaza", entry, { root: this.#debugRoot });
136
+ }
137
+ };
138
+ async function readIdeaPlazaDebug(root = process.cwd(), limit = 20) {
139
+ return readCapabilityDebug("idea-plaza", { root, limit });
140
+ }
141
+
142
+ export {
143
+ IdeaPlaza,
144
+ readIdeaPlazaDebug,
145
+ scanForSensitiveFields
146
+ };
147
+ //# sourceMappingURL=chunk-2WSISCFT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { appendCapabilityDebug, readCapabilityDebug } from \"@nebutra/capability-kit/debug\";\nimport { ContentStore } from \"@nebutra/content-store\";\nimport { assertPublicDisclosureSafe } from \"@nebutra/ecosystem-safety\";\nimport { CapabilityError } from \"@nebutra/errors\";\nimport { EventLog } from \"@nebutra/event-log\";\nimport { assetId } from \"@nebutra/generation-context\";\n\nexport type IdeaPublishLevel = \"surface\" | \"detail\" | \"cloneable\";\nexport type FeedSort = \"hot\" | \"new\" | \"near_you\";\nexport type { SensitiveField } from \"@nebutra/ecosystem-safety\";\nexport { scanForSensitiveFields } from \"@nebutra/ecosystem-safety\";\n\nexport interface CemeteryWarning {\n readonly title: string;\n readonly cause: string;\n}\n\nexport interface PublishIdeaRequest {\n readonly title: string;\n readonly oneLine: string;\n readonly level: IdeaPublishLevel;\n readonly body?: string;\n readonly tags: readonly string[];\n readonly redactions?: readonly string[];\n readonly cemeteryWarnings?: readonly CemeteryWarning[];\n}\n\nexport interface PublishedIdea {\n readonly ideaId: string;\n readonly title: string;\n readonly oneLine: string;\n readonly level: IdeaPublishLevel;\n readonly tags: readonly string[];\n readonly redactions: readonly string[];\n readonly cemeteryWarnings: readonly CemeteryWarning[];\n readonly publicationPath: string;\n readonly eventId: string;\n readonly publishedAt: string;\n}\n\nexport interface ForkRequest {\n readonly newFounderId: string;\n readonly inheritPlayPreferences?: boolean;\n}\n\nexport interface IdeaFork {\n readonly forkId: string;\n readonly sourceIdeaId: string;\n readonly newFounderId: string;\n readonly inheritPlayPreferences: boolean;\n readonly attribution: {\n readonly displayInNewCompany: true;\n readonly publicLineage: true;\n };\n readonly createdAt: string;\n}\n\nexport interface FeedRequest {\n readonly sort: FeedSort;\n readonly limit: number;\n}\n\nexport interface IdeaFeed {\n readonly items: readonly PublishedIdea[];\n}\n\nexport interface IdeaPlazaDoctorReport {\n readonly capability: \"idea-plaza\";\n readonly ok: boolean;\n readonly checkedAt: string;\n readonly features: readonly string[];\n readonly suggestion?: string;\n}\n\nexport interface IdeaPlazaOptions {\n readonly tenantId?: string;\n readonly debugRoot?: string;\n readonly contentStore?: ContentStore;\n readonly eventLog?: EventLog;\n}\n\nfunction requireTenant(value: string | undefined): string {\n if (!value?.trim()) {\n throw new CapabilityError(\"idea-plaza\", \"Idea Plaza requires tenant context\", {\n suggestion: \"Pass tenantId so publish snapshots stay tenant-scoped.\",\n statusCode: 400,\n });\n }\n return value;\n}\n\nfunction bodyForScan(request: PublishIdeaRequest): string {\n return [request.title, request.oneLine, request.body ?? \"\", request.tags.join(\" \")].join(\"\\n\");\n}\n\nexport class IdeaPlaza {\n readonly #tenantId: string;\n readonly #debugRoot: string;\n readonly #contentStore: ContentStore;\n readonly #eventLog: EventLog;\n readonly #ideas = new Map<string, PublishedIdea>();\n readonly #forks = new Map<string, IdeaFork>();\n\n private constructor(\n options: Required<Pick<IdeaPlazaOptions, \"tenantId\" | \"contentStore\" | \"eventLog\">> &\n Pick<IdeaPlazaOptions, \"debugRoot\">,\n ) {\n this.#tenantId = requireTenant(options.tenantId);\n this.#debugRoot = options.debugRoot ?? process.cwd();\n this.#contentStore = options.contentStore;\n this.#eventLog = options.eventLog;\n }\n\n static async open(\n root = \".nebutra/idea-plaza\",\n options: Omit<IdeaPlazaOptions, \"contentStore\" | \"eventLog\"> = {},\n ): Promise<IdeaPlaza> {\n const tenantId = options.tenantId ?? \"local\";\n await mkdir(root, { recursive: true });\n const contentStore = await ContentStore.open(join(root, \"content\"), { tenantId });\n const eventLog = await EventLog.open(join(root, \"event-log\"), { tenantId });\n return new IdeaPlaza({ ...options, tenantId, contentStore, eventLog });\n }\n\n async publish(request: PublishIdeaRequest): Promise<PublishedIdea> {\n assertPublicDisclosureSafe({\n capability: \"idea-plaza\",\n content: bodyForScan(request),\n ...(request.redactions !== undefined ? { redactions: request.redactions } : {}),\n suggestion: \"Review detected private values and pass redactions before publishing.\",\n });\n\n const ideaId = assetId(\"idea\", `${request.title}_${request.oneLine}`);\n const publicationPath = `plaza/ideas/${ideaId}.json`;\n const publishedAt = new Date().toISOString();\n const idea: Omit<PublishedIdea, \"eventId\"> = {\n ideaId,\n title: request.title,\n oneLine: request.oneLine,\n level: request.level,\n tags: request.tags,\n redactions: request.redactions ?? [],\n cemeteryWarnings: request.cemeteryWarnings ?? [],\n publicationPath,\n publishedAt,\n };\n const snapshot = `${JSON.stringify({ ...idea, body: request.body ?? \"\" }, null, 2)}\\n`;\n await this.#contentStore.write(publicationPath, snapshot);\n const eventId = await this.#eventLog.commit({\n traceId: assetId(\"idea_plaza_publish\", ideaId),\n kind: \"content_write\",\n affected: [publicationPath],\n parent: null,\n snapshot: { [publicationPath]: snapshot },\n });\n const published = { ...idea, eventId };\n this.#ideas.set(ideaId, published);\n await this.#debug({ type: \"publish\", tenantId: this.#tenantId, ideaId, level: request.level });\n return published;\n }\n\n async fork(ideaId: string, request: ForkRequest): Promise<IdeaFork> {\n if (!this.#ideas.has(ideaId)) {\n throw new CapabilityError(\"idea-plaza\", \"Idea fork source not found\", {\n suggestion: \"Fork only from an explicit public or cloneable snapshot in the current feed.\",\n metadata: { ideaId },\n statusCode: 404,\n });\n }\n const fork: IdeaFork = {\n forkId: assetId(\"idea_fork\", `${ideaId}_${request.newFounderId}`),\n sourceIdeaId: ideaId,\n newFounderId: request.newFounderId,\n inheritPlayPreferences: request.inheritPlayPreferences ?? false,\n attribution: {\n displayInNewCompany: true,\n publicLineage: true,\n },\n createdAt: new Date().toISOString(),\n };\n const path = `plaza/forks/${fork.forkId}.json`;\n const snapshot = `${JSON.stringify(fork, null, 2)}\\n`;\n await this.#contentStore.write(path, snapshot);\n await this.#eventLog.commit({\n traceId: assetId(\"idea_plaza_fork\", fork.forkId),\n kind: \"content_write\",\n affected: [path],\n parent: null,\n snapshot: { [path]: snapshot },\n });\n this.#forks.set(fork.forkId, fork);\n await this.#debug({ type: \"fork\", tenantId: this.#tenantId, ideaId, forkId: fork.forkId });\n return fork;\n }\n\n async feed(request: FeedRequest): Promise<IdeaFeed> {\n const items = [...this.#ideas.values()]\n .sort((left, right) =>\n request.sort === \"new\"\n ? right.publishedAt.localeCompare(left.publishedAt)\n : right.cemeteryWarnings.length - left.cemeteryWarnings.length ||\n right.publishedAt.localeCompare(left.publishedAt),\n )\n .slice(0, request.limit);\n await this.#debug({ type: \"feed\", tenantId: this.#tenantId, sort: request.sort });\n return { items };\n }\n\n async doctor(): Promise<IdeaPlazaDoctorReport> {\n const report: IdeaPlazaDoctorReport = {\n capability: \"idea-plaza\",\n ok: true,\n checkedAt: new Date().toISOString(),\n features: [\"publish\", \"redact\", \"fork-lineage\", \"feed\", \"cemetery-warning\"],\n suggestion:\n \"Local snapshots are active; public registry transport and moderation queues remain adapter handoffs.\",\n };\n await this.#debug({ type: \"doctor\", tenantId: this.#tenantId, ideas: this.#ideas.size });\n return report;\n }\n\n async close(): Promise<void> {\n await this.#contentStore.close();\n }\n\n async #debug(entry: Record<string, unknown>): Promise<void> {\n await appendCapabilityDebug(\"idea-plaza\", entry, { root: this.#debugRoot });\n }\n}\n\nexport async function readIdeaPlazaDebug(root = process.cwd(), limit = 20): Promise<unknown[]> {\n return readCapabilityDebug(\"idea-plaza\", { root, limit });\n}\n"],"mappings":"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","names":[]}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/cli.js ADDED
@@ -0,0 +1,44 @@
1
+ import {
2
+ IdeaPlaza,
3
+ readIdeaPlazaDebug
4
+ } from "./chunk-2WSISCFT.js";
5
+
6
+ // src/cli.ts
7
+ var command = process.argv[2] ?? "doctor";
8
+ var root = process.env.IDEA_PLAZA_ROOT ?? ".nebutra/idea-plaza";
9
+ var tenantId = process.env.NEBUTRA_TENANT_ID ?? "local";
10
+ if (command === "doctor") {
11
+ const plaza = await IdeaPlaza.open(root, { tenantId });
12
+ try {
13
+ process.stdout.write(`${JSON.stringify(await plaza.doctor(), null, 2)}
14
+ `);
15
+ } finally {
16
+ await plaza.close();
17
+ }
18
+ } else if (command === "quickstart") {
19
+ const plaza = await IdeaPlaza.open(root, { tenantId });
20
+ try {
21
+ const idea = await plaza.publish({
22
+ title: "Loop",
23
+ oneLine: "AI debugging for indie devs",
24
+ level: "surface",
25
+ tags: ["debugging", "developer-tools"]
26
+ });
27
+ process.stdout.write(
28
+ `${JSON.stringify({ idea, feed: await plaza.feed({ sort: "hot", limit: 5 }) }, null, 2)}
29
+ `
30
+ );
31
+ } finally {
32
+ await plaza.close();
33
+ }
34
+ } else if (command === "debug") {
35
+ process.stdout.write(
36
+ `${JSON.stringify({ capability: "idea-plaza", entries: await readIdeaPlazaDebug() }, null, 2)}
37
+ `
38
+ );
39
+ } else {
40
+ process.stderr.write(`Unknown idea-plaza command: ${command}
41
+ `);
42
+ process.exitCode = 1;
43
+ }
44
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { IdeaPlaza, readIdeaPlazaDebug } from \"./index\";\n\nconst command = process.argv[2] ?? \"doctor\";\nconst root = process.env.IDEA_PLAZA_ROOT ?? \".nebutra/idea-plaza\";\nconst tenantId = process.env.NEBUTRA_TENANT_ID ?? \"local\";\n\nif (command === \"doctor\") {\n const plaza = await IdeaPlaza.open(root, { tenantId });\n try {\n process.stdout.write(`${JSON.stringify(await plaza.doctor(), null, 2)}\\n`);\n } finally {\n await plaza.close();\n }\n} else if (command === \"quickstart\") {\n const plaza = await IdeaPlaza.open(root, { tenantId });\n try {\n const idea = await plaza.publish({\n title: \"Loop\",\n oneLine: \"AI debugging for indie devs\",\n level: \"surface\",\n tags: [\"debugging\", \"developer-tools\"],\n });\n process.stdout.write(\n `${JSON.stringify({ idea, feed: await plaza.feed({ sort: \"hot\", limit: 5 }) }, null, 2)}\\n`,\n );\n } finally {\n await plaza.close();\n }\n} else if (command === \"debug\") {\n process.stdout.write(\n `${JSON.stringify({ capability: \"idea-plaza\", entries: await readIdeaPlazaDebug() }, null, 2)}\\n`,\n );\n} else {\n process.stderr.write(`Unknown idea-plaza command: ${command}\\n`);\n process.exitCode = 1;\n}\n"],"mappings":";;;;;;AAEA,IAAM,UAAU,QAAQ,KAAK,CAAC,KAAK;AACnC,IAAM,OAAO,QAAQ,IAAI,mBAAmB;AAC5C,IAAM,WAAW,QAAQ,IAAI,qBAAqB;AAElD,IAAI,YAAY,UAAU;AACxB,QAAM,QAAQ,MAAM,UAAU,KAAK,MAAM,EAAE,SAAS,CAAC;AACrD,MAAI;AACF,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,EAC3E,UAAE;AACA,UAAM,MAAM,MAAM;AAAA,EACpB;AACF,WAAW,YAAY,cAAc;AACnC,QAAM,QAAQ,MAAM,UAAU,KAAK,MAAM,EAAE,SAAS,CAAC;AACrD,MAAI;AACF,UAAM,OAAO,MAAM,MAAM,QAAQ;AAAA,MAC/B,OAAO;AAAA,MACP,SAAS;AAAA,MACT,OAAO;AAAA,MACP,MAAM,CAAC,aAAa,iBAAiB;AAAA,IACvC,CAAC;AACD,YAAQ,OAAO;AAAA,MACb,GAAG,KAAK,UAAU,EAAE,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,MAAM,OAAO,OAAO,EAAE,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,IACzF;AAAA,EACF,UAAE;AACA,UAAM,MAAM,MAAM;AAAA,EACpB;AACF,WAAW,YAAY,SAAS;AAC9B,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,cAAc,SAAS,MAAM,mBAAmB,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EAC/F;AACF,OAAO;AACL,UAAQ,OAAO,MAAM,+BAA+B,OAAO;AAAA,CAAI;AAC/D,UAAQ,WAAW;AACrB;","names":[]}
@@ -0,0 +1,80 @@
1
+ import { ContentStore } from '@nebutra/content-store';
2
+ import { EventLog } from '@nebutra/event-log';
3
+ export { SensitiveField, scanForSensitiveFields } from '@nebutra/ecosystem-safety';
4
+
5
+ type IdeaPublishLevel = "surface" | "detail" | "cloneable";
6
+ type FeedSort = "hot" | "new" | "near_you";
7
+
8
+ interface CemeteryWarning {
9
+ readonly title: string;
10
+ readonly cause: string;
11
+ }
12
+ interface PublishIdeaRequest {
13
+ readonly title: string;
14
+ readonly oneLine: string;
15
+ readonly level: IdeaPublishLevel;
16
+ readonly body?: string;
17
+ readonly tags: readonly string[];
18
+ readonly redactions?: readonly string[];
19
+ readonly cemeteryWarnings?: readonly CemeteryWarning[];
20
+ }
21
+ interface PublishedIdea {
22
+ readonly ideaId: string;
23
+ readonly title: string;
24
+ readonly oneLine: string;
25
+ readonly level: IdeaPublishLevel;
26
+ readonly tags: readonly string[];
27
+ readonly redactions: readonly string[];
28
+ readonly cemeteryWarnings: readonly CemeteryWarning[];
29
+ readonly publicationPath: string;
30
+ readonly eventId: string;
31
+ readonly publishedAt: string;
32
+ }
33
+ interface ForkRequest {
34
+ readonly newFounderId: string;
35
+ readonly inheritPlayPreferences?: boolean;
36
+ }
37
+ interface IdeaFork {
38
+ readonly forkId: string;
39
+ readonly sourceIdeaId: string;
40
+ readonly newFounderId: string;
41
+ readonly inheritPlayPreferences: boolean;
42
+ readonly attribution: {
43
+ readonly displayInNewCompany: true;
44
+ readonly publicLineage: true;
45
+ };
46
+ readonly createdAt: string;
47
+ }
48
+ interface FeedRequest {
49
+ readonly sort: FeedSort;
50
+ readonly limit: number;
51
+ }
52
+ interface IdeaFeed {
53
+ readonly items: readonly PublishedIdea[];
54
+ }
55
+ interface IdeaPlazaDoctorReport {
56
+ readonly capability: "idea-plaza";
57
+ readonly ok: boolean;
58
+ readonly checkedAt: string;
59
+ readonly features: readonly string[];
60
+ readonly suggestion?: string;
61
+ }
62
+ interface IdeaPlazaOptions {
63
+ readonly tenantId?: string;
64
+ readonly debugRoot?: string;
65
+ readonly contentStore?: ContentStore;
66
+ readonly eventLog?: EventLog;
67
+ }
68
+ declare class IdeaPlaza {
69
+ #private;
70
+ private constructor();
71
+ static open(root?: string, options?: Omit<IdeaPlazaOptions, "contentStore" | "eventLog">): Promise<IdeaPlaza>;
72
+ publish(request: PublishIdeaRequest): Promise<PublishedIdea>;
73
+ fork(ideaId: string, request: ForkRequest): Promise<IdeaFork>;
74
+ feed(request: FeedRequest): Promise<IdeaFeed>;
75
+ doctor(): Promise<IdeaPlazaDoctorReport>;
76
+ close(): Promise<void>;
77
+ }
78
+ declare function readIdeaPlazaDebug(root?: string, limit?: number): Promise<unknown[]>;
79
+
80
+ export { type CemeteryWarning, type FeedRequest, type FeedSort, type ForkRequest, type IdeaFeed, type IdeaFork, IdeaPlaza, type IdeaPlazaDoctorReport, type IdeaPlazaOptions, type IdeaPublishLevel, type PublishIdeaRequest, type PublishedIdea, readIdeaPlazaDebug };
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ import {
2
+ IdeaPlaza,
3
+ readIdeaPlazaDebug,
4
+ scanForSensitiveFields
5
+ } from "./chunk-2WSISCFT.js";
6
+ export {
7
+ IdeaPlaza,
8
+ readIdeaPlazaDebug,
9
+ scanForSensitiveFields
10
+ };
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,9 @@
1
+ import { IdeaPlaza } from "../src/index";
2
+
3
+ const plaza = await IdeaPlaza.open(".nebutra/idea-plaza-example", { tenantId: "local" });
4
+
5
+ try {
6
+ process.stdout.write(`${JSON.stringify(await plaza.doctor(), null, 2)}\n`);
7
+ } finally {
8
+ await plaza.close();
9
+ }
@@ -0,0 +1,19 @@
1
+ import { IdeaPlaza } from "../src/index";
2
+
3
+ const plaza = await IdeaPlaza.open(".nebutra/idea-plaza-fork-example", { tenantId: "local" });
4
+
5
+ try {
6
+ const idea = await plaza.publish({
7
+ title: "Loop",
8
+ oneLine: "AI debugging for indie devs",
9
+ level: "cloneable",
10
+ tags: ["debugging"],
11
+ });
12
+ const fork = await plaza.fork(idea.ideaId, {
13
+ newFounderId: "bob",
14
+ inheritPlayPreferences: true,
15
+ });
16
+ process.stdout.write(`${JSON.stringify(fork, null, 2)}\n`);
17
+ } finally {
18
+ await plaza.close();
19
+ }
@@ -0,0 +1,16 @@
1
+ import { IdeaPlaza } from "../src/index";
2
+
3
+ const plaza = await IdeaPlaza.open(".nebutra/idea-plaza-example", { tenantId: "local" });
4
+
5
+ try {
6
+ const idea = await plaza.publish({
7
+ title: "Loop",
8
+ oneLine: "AI debugging for indie devs",
9
+ level: "surface",
10
+ body: "A developer tool that explains stack traces as a conversation.",
11
+ tags: ["debugging", "developer-tools"],
12
+ });
13
+ process.stdout.write(`${JSON.stringify(idea, null, 2)}\n`);
14
+ } finally {
15
+ await plaza.close();
16
+ }
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@nebutra/idea-plaza",
3
+ "version": "0.1.1",
4
+ "description": "Explicit opt-in idea publishing, lineage, and fork surface over Nebutra company artifacts",
5
+ "private": false,
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "nebutra": {
9
+ "status": "wip",
10
+ "productionReady": false,
11
+ "surface": "ecosystem-product",
12
+ "requires": [
13
+ "@nebutra/content-store for public snapshots",
14
+ "@nebutra/event-log for publish and fork checkpoints",
15
+ "@nebutra/ecosystem-safety for public-disclosure PII and secret checks",
16
+ "@nebutra/capability-kit for debug and doctor surfaces"
17
+ ],
18
+ "gaps": [
19
+ "Public registry transport is represented as local immutable snapshots",
20
+ "Moderation is deterministic metadata until human review queues exist",
21
+ "Cemetery similarity warnings are provided by input contracts until global KB is online"
22
+ ],
23
+ "featureId": "idea-plaza",
24
+ "category": "ai",
25
+ "summary": "Opt-in public idea snapshot and fork lineage product surface"
26
+ },
27
+ "main": "./src/index.ts",
28
+ "types": "./src/index.ts",
29
+ "exports": {
30
+ ".": "./src/index.ts"
31
+ },
32
+ "dependencies": {
33
+ "@nebutra/capability-kit": "0.2.0",
34
+ "@nebutra/content-store": "0.1.0",
35
+ "@nebutra/ecosystem-safety": "0.1.0",
36
+ "@nebutra/errors": "0.1.0",
37
+ "@nebutra/event-log": "0.1.0",
38
+ "@nebutra/generation-context": "0.1.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^22.19.15",
42
+ "tsup": "^8.5.1",
43
+ "tsx": "^4.21.0",
44
+ "typescript": "^5.9.3",
45
+ "vitest": "^4.1.4"
46
+ },
47
+ "homepage": "https://github.com/Nebutra/Nebutra-Sailor/tree/main/packages/ai/idea-plaza#readme",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/Nebutra/Nebutra-Sailor.git",
51
+ "directory": "packages/ai/idea-plaza"
52
+ },
53
+ "bugs": {
54
+ "url": "https://github.com/Nebutra/Nebutra-Sailor/issues"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "scripts": {
60
+ "build": "tsup",
61
+ "test": "vitest run",
62
+ "typecheck": "tsc --noEmit"
63
+ }
64
+ }
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: publish_idea
3
+ kind: play
4
+ version: 1.0.0
5
+ description: Publish an explicit, redacted idea snapshot for discovery and fork lineage
6
+ required_skills:
7
+ - content_store.write
8
+ - event_log.commit
9
+ sub_agents:
10
+ - role: privacy_reviewer
11
+ allowed_skills: [content_store.write]
12
+ depends_on_plays: []
13
+ ---
14
+
15
+ ## What this play does
16
+
17
+ Creates an opt-in public snapshot of an idea with lineage metadata.
18
+
19
+ ## Boundaries
20
+
21
+ - Default state is private.
22
+ - Any detected sensitive field must be redacted before publishing.
23
+ - Fork attribution remains on so the lineage graph stays trustworthy.
package/src/cli.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { IdeaPlaza, readIdeaPlazaDebug } from "./index";
2
+
3
+ const command = process.argv[2] ?? "doctor";
4
+ const root = process.env.IDEA_PLAZA_ROOT ?? ".nebutra/idea-plaza";
5
+ const tenantId = process.env.NEBUTRA_TENANT_ID ?? "local";
6
+
7
+ if (command === "doctor") {
8
+ const plaza = await IdeaPlaza.open(root, { tenantId });
9
+ try {
10
+ process.stdout.write(`${JSON.stringify(await plaza.doctor(), null, 2)}\n`);
11
+ } finally {
12
+ await plaza.close();
13
+ }
14
+ } else if (command === "quickstart") {
15
+ const plaza = await IdeaPlaza.open(root, { tenantId });
16
+ try {
17
+ const idea = await plaza.publish({
18
+ title: "Loop",
19
+ oneLine: "AI debugging for indie devs",
20
+ level: "surface",
21
+ tags: ["debugging", "developer-tools"],
22
+ });
23
+ process.stdout.write(
24
+ `${JSON.stringify({ idea, feed: await plaza.feed({ sort: "hot", limit: 5 }) }, null, 2)}\n`,
25
+ );
26
+ } finally {
27
+ await plaza.close();
28
+ }
29
+ } else if (command === "debug") {
30
+ process.stdout.write(
31
+ `${JSON.stringify({ capability: "idea-plaza", entries: await readIdeaPlazaDebug() }, null, 2)}\n`,
32
+ );
33
+ } else {
34
+ process.stderr.write(`Unknown idea-plaza command: ${command}\n`);
35
+ process.exitCode = 1;
36
+ }
@@ -0,0 +1,73 @@
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import { IdeaPlaza, readIdeaPlazaDebug, scanForSensitiveFields } from "./index";
6
+
7
+ let root: string | undefined;
8
+ let plaza: IdeaPlaza | undefined;
9
+
10
+ afterEach(async () => {
11
+ if (plaza) await plaza.close();
12
+ if (root) await rm(root, { recursive: true, force: true });
13
+ root = undefined;
14
+ plaza = undefined;
15
+ });
16
+
17
+ async function open(): Promise<IdeaPlaza> {
18
+ root = await mkdtemp(join(tmpdir(), "idea-plaza-"));
19
+ plaza = await IdeaPlaza.open(root, { tenantId: "tenant_a" });
20
+ return plaza;
21
+ }
22
+
23
+ describe("idea-plaza", () => {
24
+ it("blocks opt-in publish when sensitive data is not explicitly redacted", async () => {
25
+ const runtime = await open();
26
+
27
+ await expect(
28
+ runtime.publish({
29
+ title: "Loop",
30
+ oneLine: "AI debugging",
31
+ level: "detail",
32
+ body: "Contact alice@example.com with api key sk-test-1234567890",
33
+ tags: ["debugging"],
34
+ }),
35
+ ).rejects.toMatchObject({
36
+ capability: "idea-plaza",
37
+ suggestion: expect.stringContaining("redact"),
38
+ });
39
+ });
40
+
41
+ it("publishes explicit snapshots and preserves fork attribution", async () => {
42
+ const runtime = await open();
43
+ const published = await runtime.publish({
44
+ title: "Loop",
45
+ oneLine: "AI debugging",
46
+ level: "cloneable",
47
+ body: "Developer tool for stack trace conversations.",
48
+ tags: ["debugging", "developer-tools"],
49
+ redactions: ["customers", "finances"],
50
+ cemeteryWarnings: [{ title: "DebugMate", cause: "no PMF" }],
51
+ });
52
+
53
+ const fork = await runtime.fork(published.ideaId, {
54
+ newFounderId: "bob",
55
+ inheritPlayPreferences: true,
56
+ });
57
+ const feed = await runtime.feed({ sort: "hot", limit: 10 });
58
+
59
+ expect(published.publicationPath).toBe(`plaza/ideas/${published.ideaId}.json`);
60
+ expect(published.cemeteryWarnings).toHaveLength(1);
61
+ expect(fork.attribution.publicLineage).toBe(true);
62
+ expect(fork.sourceIdeaId).toBe(published.ideaId);
63
+ expect(feed.items.map((item) => item.ideaId)).toContain(published.ideaId);
64
+ });
65
+
66
+ it("keeps PII detection deterministic and auditable", async () => {
67
+ expect(scanForSensitiveFields("email me at a@example.com")).toEqual([
68
+ { kind: "email", value: "a@example.com" },
69
+ ]);
70
+ await open();
71
+ await expect(readIdeaPlazaDebug(root)).resolves.toEqual(expect.any(Array));
72
+ });
73
+ });